評価12: メモリ管理 - 評価基準
評価の目的
この評価では、Zigのメモリ管理システムの深い理解と、カスタムアロケータの実装能力を確認します。メモリ安全性、効率性、デバッグ可能性を兼ね備えたメモリ管理の実装スキルを評価します。
学習目標
この評価を通じて、以下の能力を確認します:
- Allocatorインターフェースの正しい実装
- メモリプールによる効率的なメモリ管理
- ArenaAllocatorを使った簡潔なメモリ管理
- GeneralPurposeAllocatorによるリーク検出
- メモリ使用量の追跡と最適化
評価項目
1. カスタムアロケータ実装(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| Allocator vtableの実装 | 6点 | alloc、resize、freeが正しく実装されている |
| 統計情報の追跡 | 6点 | 割り当て/解放量、回数、ピーク使用量を追跡 |
| 親アロケータの活用 | 4点 | 親アロケータへの委譲が適切 |
| エラーハンドリング | 4点 | メモリ不足などのエラーを適切に処理 |
確認ポイント:
- [ ] vtableの全関数が実装されている
- [ ] 統計情報が正確に更新される
- [ ] メモリリークが発生しない
- [ ] スレッドセーフ性(必要な場合)
実装評価:
// 優れた実装:統計が正確で、エラーハンドリングも適切
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
const result = self.parent_allocator.rawAlloc(len, ptr_align, ret_addr);
if (result) |ptr| {
self.total_allocated += len;
self.current_allocated += len;
self.peak_allocated = @max(self.peak_allocated, self.current_allocated);
self.allocation_count += 1;
}
return result;
}
// 不十分な実装:統計更新が不正確
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
const result = self.parent_allocator.rawAlloc(len, ptr_align, ret_addr);
self.allocation_count += 1; // total_allocatedの更新忘れ
return result;
}
2. メモリプール実装(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| プール初期化 | 5点 | 固定サイズのオブジェクトプールが正しく初期化される |
| オブジェクト確保 | 5点 | フリーリストから効率的に確保できる |
| オブジェクト解放 | 5点 | フリーリストへの返却が正しく動作する |
| 統計と再利用 | 5点 | 使用中/空きの統計が正確で、再利用が効率的 |
確認ポイント:
- [ ] フリーリストの管理が正確
- [ ] メモリの断片化が最小限
- [ ] O(1)での確保と解放
- [ ] 容量制限の正しい処理
良い実装例:
pub fn MemoryPool(comptime T: type, comptime capacity: usize) type {
return struct {
pool: [capacity]T,
free_list: [capacity]bool,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) Self {
var self = Self{
.pool = undefined,
.free_list = [_]bool{true} ** capacity,
.allocator = allocator,
};
return self;
}
pub fn alloc(self: *Self) ?*T {
for (&self.free_list, 0..) |*free, i| {
if (free.*) {
free.* = false;
return &self.pool[i];
}
}
return null; // プールが満杯
}
pub fn free(self: *Self, ptr: *T) void {
const index = (@intFromPtr(ptr) - @intFromPtr(&self.pool[0])) / @sizeOf(T);
self.free_list[index] = true;
}
};
}
3. アリーナアロケータの活用(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| JSON解析の実装 | 6点 | アリーナを使って複雑なデータ構造を構築 |
| 文字列連結 | 5点 | 効率的に複数の文字列を連結 |
| 動的配列構築 | 5点 | アリーナで動的配列を効率的に構築 |
| リソース解放 | 4点 | deinitで一括解放が正しく動作 |
確認ポイント:
- [ ] アリーナの利点を活かしている
- [ ] 一時的なメモリ確保に適切に使用
- [ ] defer arena.deinit()が適切な位置にある
- [ ] メモリリークがない
使用例の評価:
// 優れた使用例:アリーナの利点を活かしている
pub fn parseJson(arena: std.mem.Allocator, json_str: []const u8) !JsonValue {
// 複数の確保を行うが、個別にfreeする必要なし
const array = try arena.alloc(JsonValue, 3);
array[0] = JsonValue{ .Number = 1.0 };
array[1] = JsonValue{ .Number = 2.0 };
array[2] = JsonValue{ .Number = 3.0 };
// すべてdeinitで一括解放される
return JsonValue{ .Array = array };
}
// 非効率な例:アリーナを使う意味がない
pub fn parseJson(allocator: std.mem.Allocator, json_str: []const u8) !JsonValue {
const array = try allocator.alloc(JsonValue, 3);
defer allocator.free(array); // 個別にfreeしている
// アリーナの利点を活かせていない
}
4. メモリリーク検出(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| LeakyContainerの修正 | 6点 | init、deinit、cloneでリークがない |
| リーク検出の理解 | 6点 | GPAでリークを正しく検出できる |
| 修正版の実装 | 6点 | deferを使ってリークを防止 |
| テストの充実度 | 2点 | リークありとなしの両方をテスト |
確認ポイント:
- [ ] GeneralPurposeAllocatorを正しく使用
- [ ] deinitの戻り値でリークを検出
- [ ] deferを適切に配置
- [ ] すべてのアロケーションに対応する解放がある
リーク検出の例:
// 正しいリーク検出
test "memory leak detection" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const data = try allocator.alloc(u8, 1024);
defer allocator.free(data); // これがないとリーク
const leaked = gpa.deinit();
try std.testing.expect(leaked == .ok); // リークなし
}
// リークのある例
test "leaky code" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const data = try allocator.alloc(u8, 1024);
// freeを忘れている!
const leaked = gpa.deinit();
try std.testing.expect(leaked == .leak); // リーク検出
}
5. ボーナス課題(20点)
| 項目 | 配点 | 評価基準 |
|---|---|---|
| スラブアロケータ | 10点 | サイズ別プールで効率的にメモリ管理 |
| コピーオンライト | 10点 | 参照カウントと遅延コピーが正しく実装 |
ボーナス評価ポイント:
- [ ] スラブアロケータが異なるサイズクラスを扱える
- [ ] COWで不要なコピーを回避している
- [ ] 参照カウントがスレッドセーフ(必要な場合)
- [ ] メモリ効率が向上している
チェックリスト
実装前の確認
- [ ] Allocatorインターフェースを理解している
- [ ] vtableの仕組みを理解している
- [ ] メモリアライメントを考慮している
- [ ] エラーケースを洗い出している
実装後の確認
- [ ] すべてのテストが通る
- [ ] メモリリークがない(GPAで確認)
- [ ] 統計情報が正確
- [ ] エッジケースが処理されている
パフォーマンスの確認
- [ ] メモリプールの確保/解放が高速
- [ ] アリーナで一時メモリの管理が簡潔
- [ ] カスタムアロケータで統計のオーバーヘッドが小さい
- [ ] ベンチマークで効果を測定
合格基準
| レベル | 点数 | 説明 |
|---|---|---|
| 優秀 | 90-100 | すべての機能を完璧に実装、ボーナス課題も完了 |
| 良好 | 80-89 | マンダトリー課題を完全実装、ボーナス課題の一部完了 |
| 合格 | 64-79 | マンダトリー課題の80%以上を正しく実装 |
| 要再提出 | 0-63 | マンダトリー課題の実装が不十分またはリークあり |
最低合格ライン: 64点以上(マンダトリー80点満点の80%)
よくある減点ポイント
1. vtableの実装ミス(-5〜10点)
// NG: 関数シグネチャが間違っている
fn alloc(ctx: *anyopaque, len: usize) ?[*]u8 { // パラメータ不足
// ...
}
// OK: 正しいシグネチャ
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
// ...
}
2. メモリリーク(-10〜20点)
// NG: 解放忘れ
pub fn clone(self: LeakyContainer) !LeakyContainer {
const new_data = try self.allocator.alloc(u8, self.data.len);
@memcpy(new_data, self.data);
return LeakyContainer{
.data = new_data,
.allocator = self.allocator,
};
// deinitで解放されない場合、リーク
}
// OK: 正しく管理
pub fn deinit(self: *LeakyContainer) void {
self.allocator.free(self.data); // 確実に解放
}
3. 統計の不正確さ(-5点)
// NG: peak_allocatedの更新忘れ
fn alloc(...) ?[*]u8 {
// ...
self.current_allocated += len;
// peak_allocatedを更新していない
}
// OK: ピーク値も更新
fn alloc(...) ?[*]u8 {
// ...
self.current_allocated += len;
self.peak_allocated = @max(self.peak_allocated, self.current_allocated);
}
4. deferの位置ミス(-3〜5点)
// NG: deferがスコープ外
{
const data = try allocator.alloc(u8, 100);
}
defer allocator.free(data); // スコープ外でエラー
// OK: 同じスコープ内
{
const data = try allocator.alloc(u8, 100);
defer allocator.free(data);
// 使用
}
学習の手引き
メモリ管理の基礎固め
- 標準アロケータの研究
- メモリ安全性の実践
効果的な学習方法
- 小さく始める
- テスト駆動開発
- ベンチマーク測定
ピアレビューのポイント
評価者は以下の点を確認してください:
1. 動作確認
# すべてのテストを実行
zig test part1/custom_allocator.zig
zig test part2/memory_pool.zig
zig test part3/arena_usage.zig
zig test part4/leak_detection.zig
# リーク検出の確認
zig build test
2. コード確認
- Allocator vtableの実装完全性
- メモリリークの有無
- 統計情報の正確性
- エラーハンドリングの適切性
3. 理解度の確認
質問例:- 「このアロケータの利点は何ですか?」
- 「メモリプールとArenaの使い分けは?」
- 「リーク検出の仕組みを説明してください」
- Zig Language Reference - Memory: https://ziglang.org/documentation/master/#Memory
- Zig Standard Library - Heap: https://ziglang.org/documentation/master/std/#std.heap
- Ziglearn - Allocators: https://ziglearn.org/chapter-2/#allocators
- Zig Memory Patterns: https://zig.guide/standard-library/allocators
フィードバックの例
良いフィードバック
> 「カスタムアロケータの実装が非常に良く設計されています。統計情報が正確に追跡され、親アロケータへの委譲も適切です。メモリプールの実装も効率的で、O(1)での確保と解放が実現できています。ArenaAllocatorの使用例も、一時的なデータ構造の管理に理想的です。ボーナスのスラブアロケータは、実践的で性能も優れています。」
改善が必要な場合のフィードバック
> 「カスタムアロケータの基本実装は良いですが、peak_allocatedの更新が抜けています。また、LeakyContainerのclone関数でメモリリークが発生しています。allocatorを正しく保存し、deinitで解放するように修正してください。メモリプール実装のfree関数で、ポインタの検証が不足しています。範囲外のポインタが渡された場合のエラーハンドリングを追加してください。」
参考資料
まとめ
メモリ管理は、システムプログラミングの核心です。Zigのアロケータシステムは、明示的で型安全なメモリ管理を可能にします。この評価を通じて、カスタムアロケータの実装、メモリプールの設計、リーク検出など、実践的なメモリ管理スキルを習得しました。これらの技術は、パフォーマンスとメモリ安全性の両立に不可欠です。