第2章: Arenaアロケータの深層

学習目標

この章を終えると、以下ができるようになります:

  • Arenaアロケータの内部実装を完全に理解する
  • 様々なArenaパターンを実装できる
  • パフォーマンス特性を分析し、最適化できる
  • 実世界のアプリケーションで適切に使用できる

Arenaアロケータの設計哲学

メモリ管理の本質的な問題

プログラムにおけるメモリ管理の根本的な課題は、ライフタイムの管理です:

問題: いつメモリを解放すべきか?

C/C++の解決策:
  - 手動管理(malloc/free)
  - 問題: ダングリングポインタ、二重解放、メモリリーク

Java/Go/Pythonの解決策:
  - ガベージコレクション
  - 問題: STWパウス、予測不可能性

Rustの解決策:
  - 所有権システム
  - 問題: 学習曲線、借用チェッカーとの戦い

Zigの解決策:
  - 明示的アロケータ + Arenaパターン
  - 利点: 明示的、高速、予測可能

Arenaパターンの洞察

Arenaアロケータは、ライフタイムのグループ化という洞察に基づいています:

洞察:
  多くのデータは「同じタイミング」で不要になる

例1: Webリクエスト
  リクエストデータ、パースされたJSON、レスポンスバッファ
  → すべてリクエスト終了時に不要

例2: コンパイラのAST
  トークン、ASTノード、型情報
  → すべてコンパイル完了時に不要

例3: ゲームフレーム
  レンダリングコマンド、パーティクル、デバッグ情報
  → すべて次フレームで不要

この洞察により:

// 従来: N回の個別解放
for (items) |item| {
    allocator.free(item);  // N回のシステムコール
}

// Arena: 1回の一括解放
arena.deinit();  // 1回で全て解放

標準ライブラリのArenaAllocator実装

ソースコード解析

Zigの標準ライブラリのArenaAllocatorを詳しく見ていきます:

const std = @import("std");

// std.heap.ArenaAllocator の簡略版
pub const ArenaAllocator = struct {
    child_allocator: std.mem.Allocator,
    state: State,

    const State = struct {
        buffer_list: std.SinglyLinkedList(usize),
        end_index: usize,

        const BufNode = std.SinglyLinkedList(usize).Node;

        fn alloc(self: *State, child_allocator: std.mem.Allocator, len: usize, ptr_align: u8) ![]u8 {
            // 実装の詳細...
        }
    };

    pub fn init(child_allocator: std.mem.Allocator) ArenaAllocator {
        return .{
            .child_allocator = child_allocator,
            .state = .{
                .buffer_list = .{},
                .end_index = 0,
            },
        };
    }

    pub fn deinit(self: ArenaAllocator) void {
        // バッファリストを走査して全解放
        var it = self.state.buffer_list.first;
        while (it) |node| {
            const next = node.next;
            const buf = @as([*]u8, @ptrCast(node))[0..node.data];
            self.child_allocator.free(buf);
            it = next;
        }
    }

    pub fn allocator(self: *ArenaAllocator) std.mem.Allocator {
        return .{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free,
            },
        };
    }

    // ...実装の続き
};

バッファ成長戦略

標準ライブラリの成長戦略を分析します:

const std = @import("std");

pub const GrowthStrategy = struct {
    /// 初期バッファサイズ
    const INITIAL_SIZE = 4 * 1024;  // 4KB

    /// 成長率(2倍)
    const GROWTH_FACTOR = 2;

    /// 最大単一バッファサイズ
    const MAX_BUFFER_SIZE = 16 * 1024 * 1024;  // 16MB

    pub fn nextBufferSize(current_size: usize, required: usize) usize {
        if (current_size == 0) {
            return @max(INITIAL_SIZE, required);
        }

        // 2倍に成長、ただし上限あり
        var new_size = current_size * GROWTH_FACTOR;
        new_size = @min(new_size, MAX_BUFFER_SIZE);

        // 要求サイズより大きいことを保証
        return @max(new_size, required + 256);  // パディング追加
    }
};

// 成長パターンのシミュレーション
pub fn simulateGrowth() void {
    var size: usize = 0;
    std.debug.print("Buffer Growth Pattern:\n", .{});

    for (0..10) |i| {
        size = GrowthStrategy.nextBufferSize(size, 100);
        std.debug.print("  Buffer {}: {} KB\n", .{ i, size / 1024 });
    }
}

// 出力:
// Buffer 0: 4 KB
// Buffer 1: 8 KB
// Buffer 2: 16 KB
// Buffer 3: 32 KB
// Buffer 4: 64 KB
// Buffer 5: 128 KB
// Buffer 6: 256 KB
// Buffer 7: 512 KB
// Buffer 8: 1024 KB
// Buffer 9: 2048 KB

メモリレイアウトの最適化

SinglyLinkedList による管理:

メモリレイアウト:
+------------------+     +------------------+     +------------------+
| BufNode          | --> | BufNode          | --> | BufNode          |
| - next: *Node    |     | - next: *Node    |     | - next: null     |
| - data: usize    |     | - data: usize    |     | - data: usize    |
+------------------+     +------------------+     +------------------+
| [実際のバッファ]  |     | [実際のバッファ]  |     | [実際のバッファ]  |
|   4KB            |     |   8KB            |     |   16KB           |
+------------------+     +------------------+     +------------------+

利点:
  1. ノード自体がバッファの一部
     → メタデータのオーバーヘッド最小
  2. シンプルなリンクリスト
     → 実装が単純
  3. 順方向走査のみ
     → キャッシュ効率が良い

実装例:

const std = @import("std");

pub const OptimizedArena = struct {
    const BufNode = struct {
        next: ?*BufNode,
        capacity: usize,
        used: usize,

        fn getData(self: *BufNode) []u8 {
            // ノードの後ろに実際のデータがある
            const node_size = @sizeOf(BufNode);
            const ptr = @as([*]u8, @ptrCast(self)) + node_size;
            return ptr[0..self.capacity];
        }

        fn allocFromBuffer(
            self: *BufNode,
            len: usize,
            alignment: usize,
        ) ?[]u8 {
            const data = self.getData();
            const aligned_index = std.mem.alignForward(usize, self.used, alignment);

            if (aligned_index + len > self.capacity) {
                return null;  // バッファ不足
            }

            const result = data[aligned_index .. aligned_index + len];
            self.used = aligned_index + len;
            return result;
        }
    };

    parent_allocator: std.mem.Allocator,
    current: ?*BufNode,
    first: ?*BufNode,

    pub fn init(parent: std.mem.Allocator) OptimizedArena {
        return .{
            .parent_allocator = parent,
            .current = null,
            .first = null,
        };
    }

    pub fn deinit(self: *OptimizedArena) void {
        var node = self.first;
        while (node) |n| {
            const next = n.next;
            const total_size = @sizeOf(BufNode) + n.capacity;
            const ptr = @as([*]u8, @ptrCast(n));
            self.parent_allocator.free(ptr[0..total_size]);
            node = next;
        }
    }

    fn allocBuffer(self: *OptimizedArena, min_size: usize) !*BufNode {
        const node_size = @sizeOf(BufNode);
        const total_size = node_size + min_size;

        const buffer = try self.parent_allocator.alloc(u8, total_size);
        const node = @as(*BufNode, @ptrCast(@alignCast(buffer.ptr)));

        node.* = .{
            .next = null,
            .capacity = min_size,
            .used = 0,
        };

        // リストに追加
        if (self.first == null) {
            self.first = node;
        } else if (self.current) |current| {
            current.next = node;
        }
        self.current = node;

        return node;
    }

    pub fn allocator(self: *OptimizedArena) std.mem.Allocator {
        return .{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free,
            },
        };
    }

    fn alloc(
        ctx: *anyopaque,
        len: usize,
        ptr_align: u8,
        ret_addr: usize,
    ) ?[*]u8 {
        _ = ret_addr;
        const self = @as(*OptimizedArena, @ptrCast(@alignCast(ctx)));
        const alignment = @as(usize, 1) << @as(u6, @intCast(ptr_align));

        // 現在のバッファから割り当てを試みる
        if (self.current) |node| {
            if (node.allocFromBuffer(len, alignment)) |buffer| {
                return buffer.ptr;
            }
        }

        // 新しいバッファが必要
        const current_size = if (self.current) |n| n.capacity else 0;
        const new_size = if (current_size == 0)
            @max(4096, len + 256)
        else
            @max(current_size * 2, len + 256);

        const new_node = self.allocBuffer(new_size) catch return null;
        const buffer = new_node.allocFromBuffer(len, alignment) orelse return null;
        return buffer.ptr;
    }

    fn resize(
        ctx: *anyopaque,
        buf: []u8,
        buf_align: u8,
        new_len: usize,
        ret_addr: usize,
    ) bool {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = new_len;
        _ = ret_addr;
        return false;
    }

    fn free(
        ctx: *anyopaque,
        buf: []u8,
        buf_align: u8,
        ret_addr: usize,
    ) void {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = ret_addr;
    }
};

高度なArenaパターン

パターン1: ダブルバッファリングArena

ゲームエンジンで使用される技法:

const std = @import("std");

pub const DoubleBufferedArena = struct {
    arenas: [2]std.heap.ArenaAllocator,
    current_index: usize,

    pub fn init(base_allocator: std.mem.Allocator) DoubleBufferedArena {
        return .{
            .arenas = .{
                std.heap.ArenaAllocator.init(base_allocator),
                std.heap.ArenaAllocator.init(base_allocator),
            },
            .current_index = 0,
        };
    }

    pub fn deinit(self: *DoubleBufferedArena) void {
        self.arenas[0].deinit();
        self.arenas[1].deinit();
    }

    /// 現在のフレームのアロケータを取得
    pub fn currentAllocator(self: *DoubleBufferedArena) std.mem.Allocator {
        return self.arenas[self.current_index].allocator();
    }

    /// 前フレームのアロケータを取得(読み取り専用)
    pub fn previousAllocator(self: *DoubleBufferedArena) std.mem.Allocator {
        const prev = (self.current_index + 1) % 2;
        return self.arenas[prev].allocator();
    }

    /// フレームを切り替え
    pub fn swap(self: *DoubleBufferedArena) void {
        // 古いフレームをリセット(これから使うバッファ)
        const next_index = (self.current_index + 1) % 2;
        _ = self.arenas[next_index].reset(.retain_capacity);
        self.current_index = next_index;
    }
};

// 使用例: 非同期レンダリング
const RenderCommand = struct {
    position: [3]f32,
    color: [4]f32,
};

pub fn renderLoop() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var double_arena = DoubleBufferedArena.init(gpa.allocator());
    defer double_arena.deinit();

    for (0..60) |frame| {
        const current = double_arena.currentAllocator();
        const previous = double_arena.previousAllocator();

        // 新しいフレームのコマンドを構築
        var commands = std.ArrayList(RenderCommand).init(current);
        try commands.append(.{
            .position = .{ 0, 0, 0 },
            .color = .{ 1, 1, 1, 1 },
        });

        // GPUは前フレームのデータをレンダリング中
        // previousAllocatorのデータはまだ有効
        std.debug.print("Frame {}: {} commands\n", .{ frame, commands.items.len });

        // フレーム切り替え
        double_arena.swap();
    }
}

パターン2: 階層的Arena

複雑なアプリケーションでのメモリ管理:

const std = @import("std");

pub const HierarchicalArena = struct {
    /// プログラム全体で保持
    permanent: std.heap.ArenaAllocator,

    /// レベル/シーンごと
    level: std.heap.ArenaAllocator,

    /// フレームごとにリセット
    frame: std.heap.ArenaAllocator,

    /// 関数スコープの一時データ
    temp_buffer: [64 * 1024]u8,
    temp: std.heap.FixedBufferAllocator,

    pub fn init(base_allocator: std.mem.Allocator) HierarchicalArena {
        var self: HierarchicalArena = undefined;
        self.permanent = std.heap.ArenaAllocator.init(base_allocator);
        self.level = std.heap.ArenaAllocator.init(base_allocator);
        self.frame = std.heap.ArenaAllocator.init(base_allocator);
        self.temp = std.heap.FixedBufferAllocator.init(&self.temp_buffer);
        return self;
    }

    pub fn deinit(self: *HierarchicalArena) void {
        self.frame.deinit();
        self.level.deinit();
        self.permanent.deinit();
    }

    /// レベルロード時
    pub fn loadLevel(self: *HierarchicalArena) void {
        _ = self.level.reset(.free_all);
    }

    /// フレーム開始時
    pub fn newFrame(self: *HierarchicalArena) void {
        _ = self.frame.reset(.retain_capacity);
        self.temp.reset();
    }

    /// 各階層のアロケータ取得
    pub fn permanentAllocator(self: *HierarchicalArena) std.mem.Allocator {
        return self.permanent.allocator();
    }

    pub fn levelAllocator(self: *HierarchicalArena) std.mem.Allocator {
        return self.level.allocator();
    }

    pub fn frameAllocator(self: *HierarchicalArena) std.mem.Allocator {
        return self.frame.allocator();
    }

    pub fn tempAllocator(self: *HierarchicalArena) std.mem.Allocator {
        return self.temp.allocator();
    }
};

// 使用例
pub fn gameExample() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    var arena = HierarchicalArena.init(gpa.allocator());
    defer arena.deinit();

    // ゲーム設定(永続)
    const config = try arena.permanentAllocator().create(struct {
        width: u32 = 1920,
        height: u32 = 1080,
    });
    _ = config;

    // レベルロード
    arena.loadLevel();
    const level_data = try arena.levelAllocator().alloc(u8, 1024 * 1024);
    _ = level_data;

    // ゲームループ
    for (0..60) |_| {
        arena.newFrame();

        // フレーム内の処理
        const frame_data = try arena.frameAllocator().alloc(u8, 4096);
        _ = frame_data;

        // 一時データ
        fn processBatch(alloc: std.mem.Allocator) !void {
            const temp = try alloc.alloc(u8, 1024);
            defer alloc.free(temp);  // tempAllocatorはfree不要だが互換性のため
            _ = temp;
        }
        try processBatch(arena.tempAllocator());
    }
}

パターン3: スコープ付きArena

RAIIスタイルのArena管理:

const std = @import("std");

pub fn ScopedArena(comptime ResetMode: std.heap.ArenaAllocator.ResetMode) type {
    return struct {
        const Self = @This();

        arena: *std.heap.ArenaAllocator,
        parent_allocator: std.mem.Allocator,

        pub fn init(arena: *std.heap.ArenaAllocator) Self {
            return .{
                .arena = arena,
                .parent_allocator = arena.allocator(),
            };
        }

        pub fn deinit(self: *Self) void {
            _ = self.arena.reset(ResetMode);
        }

        pub fn allocator(self: *Self) std.mem.Allocator {
            return self.parent_allocator;
        }
    };
}

// 使用例
pub fn processData(base_arena: *std.heap.ArenaAllocator) !void {
    // スコープ開始
    var scoped = ScopedArena(.retain_capacity).init(base_arena);
    defer scoped.deinit();  // スコープ終了時に自動リセット

    const allocator = scoped.allocator();

    // このスコープ内でメモリを自由に使える
    const data1 = try allocator.alloc(u8, 1000);
    const data2 = try allocator.alloc(i32, 500);
    const data3 = try allocator.alloc(f64, 200);

    // 処理...
    _ = data1;
    _ = data2;
    _ = data3;

    // deferで自動的にリセットされる
}

パフォーマンス最適化

最適化1: プリアロケーション

事前にバッファを確保して再割り当てを避ける:

const std = @import("std");

pub const PreallocatedArena = struct {
    arena: std.heap.ArenaAllocator,
    preallocated: bool,

    pub fn init(base_allocator: std.mem.Allocator) PreallocatedArena {
        return .{
            .arena = std.heap.ArenaAllocator.init(base_allocator),
            .preallocated = false,
        };
    }

    pub fn deinit(self: *PreallocatedArena) void {
        self.arena.deinit();
    }

    /// 予想されるサイズを事前に確保
    pub fn preallocate(self: *PreallocatedArena, size: usize) !void {
        if (self.preallocated) return;

        // ダミー割り当てでバッファを確保
        const dummy = try self.arena.allocator().alloc(u8, size);
        _ = dummy;

        // リセットしてもキャパシティは保持
        _ = self.arena.reset(.retain_capacity);
        self.preallocated = true;
    }

    pub fn allocator(self: *PreallocatedArena) std.mem.Allocator {
        return self.arena.allocator();
    }

    pub fn reset(self: *PreallocatedArena) void {
        _ = self.arena.reset(.retain_capacity);
    }
};

// ベンチマーク
pub fn benchmarkPreallocation() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    // プリアロケーションなし
    {
        var arena = PreallocatedArena.init(gpa.allocator());
        defer arena.deinit();

        var timer = try std.time.Timer.start();
        for (0..1000) |_| {
            const data = try arena.allocator().alloc(u8, 1024);
            _ = data;
            arena.reset();
        }
        const elapsed = timer.read();
        std.debug.print("Without prealloc: {} ns\n", .{elapsed});
    }

    // プリアロケーションあり
    {
        var arena = PreallocatedArena.init(gpa.allocator());
        defer arena.deinit();

        try arena.preallocate(2 * 1024 * 1024);  // 2MB事前確保

        var timer = try std.time.Timer.start();
        for (0..1000) |_| {
            const data = try arena.allocator().alloc(u8, 1024);
            _ = data;
            arena.reset();
        }
        const elapsed = timer.read();
        std.debug.print("With prealloc:    {} ns\n", .{elapsed});
    }
}

最適化2: アライメント最適化

const std = @import("std");

pub const AlignmentOptimizedArena = struct {
    arena: std.heap.ArenaAllocator,
    cache_line_size: usize,

    pub fn init(base_allocator: std.mem.Allocator) AlignmentOptimizedArena {
        return .{
            .arena = std.heap.ArenaAllocator.init(base_allocator),
            .cache_line_size = 64,  // 典型的なキャッシュラインサイズ
        };
    }

    pub fn deinit(self: *AlignmentOptimizedArena) void {
        self.arena.deinit();
    }

    /// キャッシュラインアラインされた割り当て
    pub fn allocCacheAligned(
        self: *AlignmentOptimizedArena,
        comptime T: type,
        n: usize,
    ) ![]T {
        const allocator = self.arena.allocator();
        const alignment = @max(@alignOf(T), self.cache_line_size);
        return allocator.alignedAlloc(T, alignment, n);
    }

    /// SIMD用のアライメント(AVX2: 32バイト)
    pub fn allocSIMDAligned(
        self: *AlignmentOptimizedArena,
        comptime T: type,
        n: usize,
    ) ![]T {
        const allocator = self.arena.allocator();
        return allocator.alignedAlloc(T, 32, n);
    }
};

最適化3: メモリプールとの組み合わせ

const std = @import("std");

pub const HybridArena = struct {
    /// 小さい割り当て用(< 256バイト)
    small_pool: std.heap.MemoryPool(SmallBlock),

    /// 中程度の割り当て用(256バイト - 4KB)
    medium_arena: std.heap.ArenaAllocator,

    /// 大きい割り当て用(> 4KB)
    large_allocator: std.mem.Allocator,

    const SmallBlock = struct {
        data: [256]u8,
    };

    pub fn init(base_allocator: std.mem.Allocator) HybridArena {
        return .{
            .small_pool = std.heap.MemoryPool(SmallBlock).init(base_allocator),
            .medium_arena = std.heap.ArenaAllocator.init(base_allocator),
            .large_allocator = base_allocator,
        };
    }

    pub fn deinit(self: *HybridArena) void {
        self.small_pool.deinit();
        self.medium_arena.deinit();
    }

    pub fn allocator(self: *HybridArena) std.mem.Allocator {
        return .{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free,
            },
        };
    }

    fn alloc(
        ctx: *anyopaque,
        len: usize,
        ptr_align: u8,
        ret_addr: usize,
    ) ?[*]u8 {
        const self = @as(*HybridArena, @ptrCast(@alignCast(ctx)));

        if (len <= 256) {
            // 小さい割り当て: プールから
            const block = self.small_pool.create() catch return null;
            return @ptrCast(&block.data);
        } else if (len <= 4096) {
            // 中程度: Arenaから
            return self.medium_arena.allocator().vtable.alloc(
                self.medium_arena.allocator().ptr,
                len,
                ptr_align,
                ret_addr,
            );
        } else {
            // 大きい: 直接割り当て
            return self.large_allocator.vtable.alloc(
                self.large_allocator.ptr,
                len,
                ptr_align,
                ret_addr,
            );
        }
    }

    fn resize(
        ctx: *anyopaque,
        buf: []u8,
        buf_align: u8,
        new_len: usize,
        ret_addr: usize,
    ) bool {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = new_len;
        _ = ret_addr;
        return false;
    }

    fn free(
        ctx: *anyopaque,
        buf: []u8,
        buf_align: u8,
        ret_addr: usize,
    ) void {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = ret_addr;
    }
};

実世界のケーススタディ

ケース1: Zigコンパイラ

Zigコンパイラのメモリ管理戦略:

Phase 1: Tokenization
  Arena: Token Arena
  ライフタイム: パース完了まで

Phase 2: Parsing
  Arena: AST Arena
  ライフタイム: セマンティック解析完了まで

Phase 3: Semantic Analysis
  Arena: Type Arena (永続)
  Arena: 関数ごとのローカルArena
  ライフタイム: コンパイル全体 / 関数解析中

Phase 4: Code Generation
  Arena: IR Arena
  ライフタイム: 最適化パスごと

メモリ使用量:
  小規模プロジェクト: ~50MB
  大規模プロジェクト: ~500MB
  ピーク時: AST + 型情報

ケース2: ゲームエンジン(Unity相当)

階層的メモリ管理:

1. Global Heap (GPA)
   ├─ Permanent Arena
   │  └─ エンジン設定、グローバルリソース
   │
   ├─ Scene Arena
   │  └─ シーンオブジェクト、スクリプト
   │
   ├─ Frame Arena (Double Buffered)
   │  ├─ Current Frame
   │  │  └─ レンダリングコマンド、パーティクル
   │  └─ Previous Frame (GPU読み取り中)
   │
   └─ Temp Stack (64KB固定)
      └─ 関数ローカルの一時データ

メモリ使用量(典型的なシーン):
  Permanent: 100MB
  Scene:     500MB
  Frame:     10MB/frame
  Temp:      64KB(固定)

ケース3: Webサーバー(Hono相当)

const std = @import("std");

const WebServer = struct {
    const Request = struct {
        method: []const u8,
        path: []const u8,
        headers: std.StringHashMap([]const u8),
        body: []const u8,
    };

    const Response = struct {
        status: u16,
        headers: std.StringHashMap([]const u8),
        body: []const u8,
    };

    fn handleRequest(
        base_allocator: std.mem.Allocator,
        raw_request: []const u8,
    ) !Response {
        // リクエストスコープArena
        var arena = std.heap.ArenaAllocator.init(base_allocator);
        defer arena.deinit();

        const allocator = arena.allocator();

        // パース(メモリを自由に使える)
        const request = try parseRequest(allocator, raw_request);

        // ルーティング
        if (std.mem.eql(u8, request.path, "/api/users")) {
            return try handleUsers(allocator, request);
        }

        // デフォルトレスポンス
        return Response{
            .status = 404,
            .headers = std.StringHashMap([]const u8).init(allocator),
            .body = try allocator.dupe(u8, "Not Found"),
        };
    }

    fn parseRequest(
        allocator: std.mem.Allocator,
        raw: []const u8,
    ) !Request {
        return Request{
            .method = try allocator.dupe(u8, "GET"),
            .path = try allocator.dupe(u8, "/api/users"),
            .headers = std.StringHashMap([]const u8).init(allocator),
            .body = try allocator.dupe(u8, raw),
        };
    }

    fn handleUsers(
        allocator: std.mem.Allocator,
        request: Request,
    ) !Response {
        _ = request;
        var response = Response{
            .status = 200,
            .headers = std.StringHashMap([]const u8).init(allocator),
            .body = undefined,
        };

        try response.headers.put(
            try allocator.dupe(u8, "Content-Type"),
            try allocator.dupe(u8, "application/json"),
        );

        response.body = try allocator.dupe(u8, "{\"users\": []}");
        return response;
    }
};

まとめ

Arenaアロケータの深層を学びました:

  • 設計哲学: ライフタイムのグループ化
  • 内部実装: SinglyLinkedList、成長戦略
  • 高度なパターン: ダブルバッファリング、階層的管理
  • 最適化技法: プリアロケーション、アライメント
  • 実世界の応用: コンパイラ、ゲーム、Webサーバー
  • 次の章では、Poolアロケータについて学びます。固定サイズのオブジェクトを効率的に管理する技法を探ります。

    参考文献

  • Zig Standard Library - ArenaAllocator: https://github.com/ziglang/zig/blob/master/lib/std/heap/arena_allocator.zig
  • Ginger Bill - Memory Allocation Strategies: https://www.gingerbill.org/series/memory-allocation-strategies/
  • Niklaus Wirth - Algorithms + Data Structures = Programs (1976)