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

アロケータの選択

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が利用できます。ベアメタル / 組込みシステムで固定長のバッファをアロケータで利用する場合にも、これらのアロケータは有用です。これらのアロケータには、[]mut u8のスライスを.bufferフィールドに渡します。

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

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

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

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

test "page_allocator" {
    var allocator = heap.page_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(&mut arena, u32);
}

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

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

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

最後に、WebAssemblyをビルドする場合、std.heap.page_allocator を利用して下さい。ターゲットがWebAssemblyのときにはビルド時に page_allocator 内で自動的にWebAssembly用のアロケータが選択されます。

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

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

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

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

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

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

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

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

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

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 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.