オフィスアワーがそろそろ始まるよ!()

アロケータの選択

Zen標準ライブラリで提供されているアロケータを紹介し、その選択方法について説明します。各アロケータは初期化方法が異なりますが、基本的な利用方法は同じです。ただし、ArenaAllocatorに関してはアリーナのメモリを全て解放するという操作が可能です。

アロケータの選択

まず、libcをリンクする場合、C言語のアロケータをラッピングしたstd.heap.c_allocatorが有力候補です。このアロケータはlibcreallocを呼ぶことでメモリを管理しています。c_allocatorはグローバルなインスタンスが用意されているため、libcさえリンクされていれば、初期化なしで動きます。

examples/ch08-memory/src/allocators.zen:7:11

test "c_allocator" {
    var allocator = heap.c_allocator;
    var allocated = try heap.create(&allocator, u32);
    defer heap.destroy(&allocator, allocated);
}

固定長のバッファによるアロケーションで十分な場合、std.heap.FixedBufferAllocatorもしくはstd.heap.ThreadSafeFixedBufferAllocatorが利用できます。ベアメタル / 組込みシステムで固定長のバッファをアロケータで利用する場合にも、これらのアロケータは有用です。これらのアロケータには、[]u8のスライスを.bufferフィールドに渡します。

examples/ch08-memory/src/allocators.zen:13:18

test "FixedBufferAllocator" {
    var buffer: [100]u8 = undefined;
    var allocator = heap.FixedBufferAllocator { .buffer = &buffer };
    var allocated = try heap.create(&allocator, u32);
    defer heap.destroy(&allocator, allocated);
}

direct_allocatorは、OS APIを使って (WindowsではVirtualAlloc、それ以外ではmmap) メモリを確保します。direct_allocatorはグローバルなインスタンスが用意されているため、(Linux / Windows / MacOSX上で動くアプリケーションであれば) 初期化なしで動きます。

examples/ch08-memory/src/allocators.zen:20:24

test "direct_allocator" {
    var allocator = heap.direct_allocator;
    var allocated = try heap.create(&allocator, u32);
    defer heap.destroy(&allocator, allocated);
}

ArenaAllocatorは短命のコマンドラインアプリケーションや、周期的なパターンのあるアプリケーションで効力を発揮するでしょう。このアロケータは、割り当てメモリ領域を記憶し、deinitにより確保したメモリを全て解放します。ArenaAllocatorは他のアロケータのラッパーアロケータとして動作します。そのため、初期化では他のアロケータをallocatorフィールドに渡します。

examples/ch08-memory/src/allocators.zen:26:30

test "ArenaAllocator" {
    var arena = heap.ArenaAllocator { .allocator = &heap.c_allocator };
    defer arena.deinit();
    var allocated = try heap.create(&arena, u32);
}

error.OutOfMemory発生時のエラー処理をテストしたい場合、std.debug.FailingAllocatorを利用して下さい。FailingAllocatorは N 回メモリを割り当てた後、次のメモリ割り当てでerror.OutOfMemoryを返します。アロケータの初期化では、fail_indexinternal_allocatorを渡します。fail_indexに N を指定します。

examples/ch08-memory/src/allocators.zen:32:42

test "FailingAllocator" {
    var allocator = std.debug.FailingAllocator {
        .fail_index = 2,
        .internal_allocator = &heap.c_allocator,
    };
    _ = try heap.create(&allocator, u32);
    _ = try heap.create(&allocator, u32);
    // 2回メモリを割り当てた後、次の割り当ては失敗する
    const result = heap.create(&allocator, u32);
    err(error.OutOfMemory, result);
}

最後に、WebAssemblyをビルドする場合、std.heap.wasm_allocatorを利用しましょう。このアロケータはWebAssemblyのメモリ命令を使用します。

上記選択肢に当てはまらない場合、新たなアロケータを実装することが可能です。

新しいアロケータを実装するためのヒント

新たなアロケータを実装するためには、std.heap.Allocatorインタフェースを実装します。このインタフェースは2つのメソッドreallocshrinkの実装を要求します。

reallocはメモリの再割当て / アライメントの変更、を行うための関数で、メモリを新たに割り当てる場合にも利用されます。shrinkは確保済みメモリを縮小 / アライメントを変更、するための関数で、メモリを解放する場合にも利用されます。

pub const Allocator = interface {
    pub const Error = error{OutOfMemory};

    pub fn realloc(
        old_mem: []u8,
        old_alignment: u29,
        new_byte_count: usize,
        new_alignment: u29,
    ) Error![]u8;

    pub fn shrink(
        old_mem: []u8,
        old_alignment: u29,
        new_byte_count: usize,
        new_alignment: u29,
    ) []u8;

ライブラリ作成者へのアドバイス

具体的なアロケータを使用せず、Allocatorインタフェースを引数として受け取るAPIを設計しましょう。そのようにすることで、ライブラリのユーザが利用するアロケータを決定することができます。

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.