🦖

[ZIG] 各アロケータの解説

2025/01/15に公開

はじめに

皆さん、zig使ってますか!?zigはよくrustやc3、jaiなどと比べられますが個人的にはどの言語も長所や短所があると思います。そして、zigの良い点である要素にメモリ管理の自由度があげられます。ただ、自由度が高いということは学習難易度が比較的高いというデメリットとトレードオフです。実のところ自身も完全な把握は出来ていません。間違えている箇所がある場合はコメント等で教えてほしいです。今回使用するzigのバージョンは0.14.0-dev.2563+af5e73172です。

アロケータの種類

General Purpose Allocator

汎用アロケータです、速度よりも安全性を重視していて二重開放を検知する機能とメモリリークを検知する機能を持っています。構造体で指定することで、安全性チェックとスレッドセーフティーをオフに出来ます。二重開放の検知はそもそもzigの言語仕様で、os保護機能によるセグメントフォルトが発生するためGPAでいうところの二重開放を検知する機能は、gpa内部でメモリブロックにヘッダを付け開放時にマークすることで内部メカニズムとしての二重開放検知という解釈だと思います。
自身の環境ではgpaの二重開放検知機能よりも先にセグメントフォルトが発生してしまい再現が出来ませんでした。

  • メモリリークテスト
test "gpa double free check with defer" {
    var gpa = std.heap.GeneralPurposeAllocator(.{}).init;
    const allocator = gpa.allocator();
    defer {
        const result = gpa.deinit();

        if (result == .leak) {
            std.debug.print("Memory leak detected\n", .{});
        }

        if (result == .ok) {
            std.debug.print("Memory deallocated\n", .{});
        }
    }
    // deferで開放していなことによるリーク
    _ = try allocator.alloc(u8, 100);
}

このコードではメモリリークエラーが発生すると思います。しっかりと機能していますね。ちなみにdeferは上記の様にブロックを使用してクリーンアップコードと言われる、スコープを抜ける時に必ず実行される処理を記述できます。

PageAllocator

PageAllocatorは呼び出された時にosにメモリの全ページを要求します。単一のバイト割当に複数のキビバイトを要求する可能性が高いです。そしてosにシステムコールを介してメモリの要求をするため単一のバイトに複数のキビバイトを要求する点とosにシステムコールを介してメモリの要求をする点、この2つの理由から小さなメモリ割り当てをする場合、メモリ使用量と速度の両方で非効率な設計になっています。(とはいえgpaよりは高速)逆に大きなメモリ割り当てをする場合は設計がシンプルな点gpaのようなメタデータが存在しないためオーバーヘッドが少ないので高速になります。

  • メモリリークテスト
test "page allocator memory leak check" {
    const allocator = std.heap.page_allocator;

    // メモリリークが起きる
    _ = try allocator.alloc(u8, 1000);
}

この様なコードで意図的にメモリリークを発生させた場合、gpaではパニックを発生させることが出来ましたがpage allocatorでは問題なくビルドできてしまいました。そもそもgpaのようにdenit();関数が無いためリークを検知することが出来ません。そう考えるとやはり遅くても重要なコードではgpaを使用するのが良いでしょう。


 defer {
        const result = gpa.deinit();

        if (result == .leak) {
            std.debug.print("Memory leak detected\n", .{});
        }

        if (result == .ok) {
            std.debug.print("Memory deallocated\n", .{});
        }
    }

FixedBufferAllocator

FixedBufferAllocatorは固定バッファーをアロケータに割り当てるアロケータです。
ヒープ割当を使用しないので速度は早いです。固定バッファーということですのでサイズを再割り当てなどは出来ず使用できる範囲を超えるとOutOfMemoryを発生させます。

  • メモリリークテスト
test "fixed buffer allocator memory leak check" {
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();

    _ = try allocator.alloc(u8, 8);
    //  try allocator.alloc(u8,1025); OutOfMemory
}

もちろんメモリリークは検知出来ません。

ArenaAllocator

ArenaAllocatorは子要素のアロケータを受け取り何度も割当をすることが出来ます。開放するのは一回のみで全ての子要素のアロケータを開放します。

  • メモリリークテスト
test "arena allocator memory leak check" {
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    const allocator = arena.allocator();

    _ = try allocator.alloc(u8, 8);
    _ = try allocator.alloc(u8, 16);
    _ = try allocator.alloc(u8, 32);
}

結果はgpa以外と同じです。Arenaを使う時は子要素のアロケータで操作をするということはなるべく避け、必ずarena.deinit();を呼び出すように気をつければ良いと思います。

C_Allocator

C_AllocatorはC言語と互換があるアロケータです。mallocやfreeを直接使用したい場合はraw_c_allocatorというアロケータを使用する必要があります。使用方法は他のgpa以外のアロケータとさほど変わりません。

  • テストコード
test "c_allocator" {
    var c = std.heap.c_allocator;
    const memory = try c.alloc(u8, 10);
    try std.testing.expect(memory.len == 10);
    defer c.free(memory);
    std.debug.print("c_allocator memory leak check {d}\n", .{memory.len});
}

そして使用するにはLibCと連携する必要があります。build.zigにリンクするように書きましょう。

  • build.zig
 const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, .optimize = .Debug });
    exe_unit_tests.linkLibC();

これで起動するはずです。

簡易ベンチマーク

各アロケータのベンチマークを簡易的に行ってみました。
与えたバイト数はこちらです。

 const small_size = 8;
 const medium_size = 1024;
 const large_size = 20480;

結果はこちら

PageAllocator FixedBufferAllocator ArenaAllocator C_Allocator GPA
2307ns 257ns 1874ns 4419ns 21242ns
1041ns 132ns 307ns 190ns 9860ns
2775ns 278ns 3420ns 2405ns 11476ns

最後に

今回は自分で調べたzigのアロケータを書いてみました。まだ説明していないアロケータでwasmアロケータなどがありますが、将来gpaと統合されると書かれているのでそうなってから追記するかもです。zigは公式ドキュメントよりもこのサイトの方が分かりやすく作ってる人に感謝です。そんな公式もメモリの使用についてYoutubeで動画を上げているので勉強になりました。そしてzigは最初はエラーばっかで難しいと思うかもしれませんが凄く良い言語だと思うので利用者がもっと増えてほしいナと個人的に思います。

参考文献

zig公式 std.heap
zig guide

Discussion