🙄

ZigのAllocator まとめ

2022/11/05に公開1

ZigのAllocator まとめ

最近Zigに入門中。Allocatorについて調べたのでまとめる。

Allocators

Zigでは動的なメモリ管理のパターンが、標準ライブラリでいくつか提供されている。

std.heap.page_allocator

最も一般的なallocator。
OSにページ単位でメモリ要求する。最小単位がページのため、1byteのみ欲しい時など非効率な場合がある。

test "allocation" {
    // page_allocatorの生成
    const allocator = std.heap.page_allocator;

    // 100byteを確保
    const memory = try allocator.alloc(u8, 100);
    // スコープを出ると同時に確保した100byteを開放
    defer allocator.free(memory);

    try expect(memory.len == 100);
    try expect(@TypeOf(memory) == []u8);
}

std.heap.FixedBufferAllocator

固定長のメモリ割当を行う。ヒープ割当は行わないらしい。
カーネルを書く時など、ヒープ割当が望めない場合に利用できる。
実際の割当はコンパイル時に処理される?(スタック上に確保されるのか?)

test "fixed buffer allocator" {
    // キャパシティ情報だけ確保?
    var buffer: [1000]u8 = undefined;
    // ポインタを渡してallocatorを初期化
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    // FixedBufferAllocatorの生成
    const allocator = fba.allocator();

    // 100byteを確保
    const memory = try allocator.alloc(u8, 100);
    // スコープを出ると同時に確保した100byteを開放 (スタックに確保されるとするなら普通に開放される気がするが...)
    defer allocator.free(memory);

    try expect(memory.len == 100);
    try expect(@TypeOf(memory) == []u8);
}

std.heap.ArenaAllocator

複数回割当を実行できて、1回で開放できる。
開放するときはfreeじゃなくてdeinitらしい。
実際に割当を行うallocatorは別途指定する。

test "arena allocator" {
    // page_allocatorを指定してallocatorを初期化
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    // スコープを抜けると同時に確保したメモリを一括開放
    defer arena.deinit();
    // ArenaAllAllocatorを生成
    const allocator = arena.allocator();

    // 複数回メモリを確保
    _ = try allocator.alloc(u8, 1);
    _ = try allocator.alloc(u8, 10);
    _ = try allocator.alloc(u8, 100);
}

std.heap.GeneralPurposeAllocator

複数回のfreeやメモリのリークなどが検知できる。

test "GPA" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    // GeneralPurposeAllocatorを生成
    const allocator = gpa.allocator();
    defer {
        // 確保したメモリを開放 (2回目)
        const leaked = gpa.deinit();
	// 2重開放が戻り値で検知できる
        if (leaked) expect(false) catch @panic("TEST FAIL"); //fail test; can't try in defer as defer is executed after we return
    }
    
    // 100byteを確保
    const bytes = try allocator.alloc(u8, 100);
    // 確保したメモリを開放 (1回目)
    defer allocator.free(bytes);
}

std.heap.c_allocator

安全性は低いけどパフォーマンスが高いらしい。

Discussion

funatsufumiyafunatsufumiya

とてもわかりやすいまとめで、zig入門の良い参考になりました。

FixedBufferAllocatorについて調べてみたのですが、100行程度の実装で特に難しい内容ではなくて、スタック上の固定長割当領域をまるでヒープのように扱えるものでした。
https://github.com/ziglang/zig/blob/76aa1fffb7a06f0be0d803cb3379f3102c0b2590/lib/std/heap.zig#L366

ちなみに var buffer: [1000]u8 = undefined; の意味は以下記事がわかりやすくて、要約するとキャパシティは確保したいがメモリの中身は気にしないとコンパイラに伝える、とあり、まさにアロケータで利用することを念頭にした構文のようです。
https://zig.news/kristoff/what-s-undefined-in-zig-9h