課題12: メモリ管理の実践
マンダトリー要件 (80点)
Part 1: カスタムアロケータ実装 (20点)
統計情報を記録するカスタムアロケータを実装してください。
ファイル: part1/custom_allocator.zig
const std = @import("std");
const TrackingAllocator = struct {
parent_allocator: std.mem.Allocator,
total_allocated: usize,
total_freed: usize,
current_allocated: usize,
peak_allocated: usize,
allocation_count: usize,
free_count: usize,
const Self = @This();
pub fn init(parent: std.mem.Allocator) Self {
return Self{
.parent_allocator = parent,
.total_allocated = 0,
.total_freed = 0,
.current_allocated = 0,
.peak_allocated = 0,
.allocation_count = 0,
.free_count = 0,
};
}
pub fn allocator(self: *Self) std.mem.Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
// TODO: alloc関数を実装
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
// 実装
}
// TODO: resize関数を実装
fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
new_len: usize,
ret_addr: usize,
) bool {
// 実装
}
// TODO: free関数を実装
fn free(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
ret_addr: usize,
) void {
// 実装
}
pub fn printStats(self: Self) void {
std.debug.print("=== Allocator Statistics ===\n", .{});
std.debug.print("Total allocated: {} bytes\n", .{self.total_allocated});
std.debug.print("Total freed: {} bytes\n", .{self.total_freed});
std.debug.print("Currently allocated: {} bytes\n", .{self.current_allocated});
std.debug.print("Peak allocation: {} bytes\n", .{self.peak_allocated});
std.debug.print("Allocation count: {}\n", .{self.allocation_count});
std.debug.print("Free count: {}\n", .{self.free_count});
}
};
pub fn main() !void {
var tracking = TrackingAllocator.init(std.heap.page_allocator);
const allocator = tracking.allocator();
const data1 = try allocator.alloc(u8, 1024);
const data2 = try allocator.alloc(u8, 2048);
const data3 = try allocator.alloc(u8, 512);
allocator.free(data1);
allocator.free(data2);
const data4 = try allocator.alloc(u8, 4096);
allocator.free(data3);
allocator.free(data4);
tracking.printStats();
}
Part 2: メモリプール実装 (20点)
固定サイズのオブジェクトを効率的に管理するメモリプールを実装してください。
ファイル: part2/memory_pool.zig
const std = @import("std");
fn MemoryPool(comptime T: type, comptime capacity: usize) type {
return struct {
pool: [capacity]T,
free_list: [capacity]bool,
allocator: std.mem.Allocator,
const Self = @This();
// TODO: 初期化
pub fn init(allocator: std.mem.Allocator) Self {
// 実装
}
// TODO: オブジェクトを確保
pub fn alloc(self: *Self) ?*T {
// 実装
}
// TODO: オブジェクトを解放
pub fn free(self: *Self, ptr: *T) void {
// 実装
}
// TODO: 統計情報を取得
pub fn stats(self: Self) struct {
total: usize,
used: usize,
free: usize,
} {
// 実装
}
// TODO: すべてを解放
pub fn reset(self: *Self) void {
// 実装
}
};
}
const Node = struct {
value: i32,
next: ?*Node,
};
pub fn main() !void {
var pool = MemoryPool(Node, 10).init(std.heap.page_allocator);
std.debug.print("=== Memory Pool ===\n\n", .{});
// ノードを確保してリンクリストを作成
const n1 = pool.alloc() orelse return error.OutOfMemory;
const n2 = pool.alloc() orelse return error.OutOfMemory;
const n3 = pool.alloc() orelse return error.OutOfMemory;
n1.* = .{ .value = 1, .next = n2 };
n2.* = .{ .value = 2, .next = n3 };
n3.* = .{ .value = 3, .next = null };
var s = pool.stats();
std.debug.print("After allocation: {}/{} used\n", .{s.used, s.total});
// リンクリストを辿る
var current: ?*Node = n1;
std.debug.print("List: ", .{});
while (current) |node| {
std.debug.print("{} -> ", .{node.value});
current = node.next;
}
std.debug.print("null\n\n", .{});
// 一部を解放
pool.free(n2);
s = pool.stats();
std.debug.print("After free: {}/{} used\n", .{s.used, s.total});
// 再確保
const n4 = pool.alloc() orelse return error.OutOfMemory;
n4.* = .{ .value = 4, .next = null };
s = pool.stats();
std.debug.print("After realloc: {}/{} used\n", .{s.used, s.total});
}
Part 3: アリーナアロケータの活用 (20点)
ArenaAllocatorを使った効率的なメモリ管理を実装してください。
ファイル: part3/arena_usage.zig
const std = @import("std");
const JsonValue = union(enum) {
Null,
Bool: bool,
Number: f64,
String: []const u8,
Array: []JsonValue,
Object: std.StringHashMap(JsonValue),
};
// TODO: Arenaを使ってJSONをパース(簡易版)
fn parseJson(arena: std.mem.Allocator, json_str: []const u8) !JsonValue {
_ = json_str;
// 実装(簡略化のため固定値を返す)
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 };
return JsonValue{ .Array = array };
}
// TODO: Arenaを使って文字列を連結
fn concatStrings(arena: std.mem.Allocator, strings: []const []const u8) ![]const u8 {
// 実装
}
// TODO: Arenaを使って動的配列を構築
fn buildDynamicArray(arena: std.mem.Allocator, count: usize) ![]i32 {
// 実装
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
std.debug.print("=== Arena Allocator Usage ===\n\n", .{});
// JSON パース
const json = try parseJson(allocator, "[1, 2, 3]");
switch (json) {
.Array => |arr| {
std.debug.print("Parsed array with {} elements\n\n", .{arr.len});
},
else => {},
}
// 文字列連結
const parts = [_][]const u8{ "Hello", ", ", "World", "!" };
const concatenated = try concatStrings(allocator, &parts);
std.debug.print("Concatenated: {s}\n\n", .{concatenated});
// 動的配列
const array = try buildDynamicArray(allocator, 10);
std.debug.print("Dynamic array: ", .{});
for (array) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
// すべてのメモリが一度に解放される
}
Part 4: メモリリーク検出 (20点)
GeneralPurposeAllocatorを使ってメモリリークを検出し、修正してください。
ファイル: part4/leak_detection.zig
const std = @import("std");
const LeakyContainer = struct {
data: []u8,
allocator: std.mem.Allocator,
// TODO: 初期化(正しく実装)
pub fn init(allocator: std.mem.Allocator, size: usize) !LeakyContainer {
// 実装
}
// TODO: 解放(忘れずに実装)
pub fn deinit(self: *LeakyContainer) void {
// 実装
}
// TODO: データをコピー
pub fn clone(self: LeakyContainer) !LeakyContainer {
// 実装(メモリリークに注意)
}
};
// TODO: リークのあるバージョン
fn processDataLeaky(allocator: std.mem.Allocator) !void {
const data = try allocator.alloc(u8, 1024);
// freeを忘れている!
_ = data;
}
// TODO: 修正版
fn processDataFixed(allocator: std.mem.Allocator) !void {
const data = try allocator.alloc(u8, 1024);
defer allocator.free(data);
// 処理
for (data, 0..) |*byte, i| {
byte.* = @intCast(i % 256);
}
}
pub fn main() !void {
std.debug.print("=== Memory Leak Detection ===\n\n", .{});
// テスト1: リークあり
{
std.debug.print("Test 1: Leaky version\n", .{});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
try processDataLeaky(allocator);
const leaked = gpa.deinit();
if (leaked == .leak) {
std.debug.print("Result: LEAK DETECTED ❌\n\n", .{});
}
}
// テスト2: 修正版
{
std.debug.print("Test 2: Fixed version\n", .{});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
try processDataFixed(allocator);
const leaked = gpa.deinit();
if (leaked == .ok) {
std.debug.print("Result: NO LEAKS ✓\n\n", .{});
}
}
// テスト3: Container
{
std.debug.print("Test 3: Container\n", .{});
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var container = try LeakyContainer.init(allocator, 512);
defer container.deinit();
const leaked = gpa.deinit();
if (leaked == .ok) {
std.debug.print("Result: NO LEAKS ✓\n", .{});
}
}
}
ボーナス課題 (20点)
Bonus 1: スラブアロケータ (10点)
異なるサイズのオブジェクトを効率的に管理するスラブアロケータを実装してください。
ファイル: bonus1/slab_allocator.zig
const std = @import("std");
const SlabAllocator = struct {
small_pool: [64]*[32]u8,
medium_pool: [32]*[128]u8,
large_pool: [16]*[512]u8,
parent_allocator: std.mem.Allocator,
const Self = @This();
// TODO: 実装
pub fn init(parent: std.mem.Allocator) !Self {
// 実装
}
pub fn deinit(self: *Self) void {
// 実装
}
pub fn allocator(self: *Self) std.mem.Allocator {
// 実装
}
// TODO: サイズに応じて適切なプールから確保
fn alloc(
ctx: *anyopaque,
len: usize,
ptr_align: u8,
ret_addr: usize,
) ?[*]u8 {
_ = ctx;
_ = 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;
// 実装
}
fn free(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
ret_addr: usize,
) void {
_ = ctx;
_ = buf;
_ = buf_align;
_ = ret_addr;
// 実装
}
};
pub fn main() !void {
var slab = try SlabAllocator.init(std.heap.page_allocator);
defer slab.deinit();
const allocator = slab.allocator();
const small = try allocator.alloc(u8, 16);
defer allocator.free(small);
const medium = try allocator.alloc(u8, 64);
defer allocator.free(medium);
const large = try allocator.alloc(u8, 256);
defer allocator.free(large);
std.debug.print("Allocated small, medium, and large buffers\n", .{});
}
Bonus 2: コピーオンライト (10点)
コピーオンライトの仕組みを実装してください。
ファイル: bonus2/copy_on_write.zig
const std = @import("std");
fn CowString(comptime max_size: usize) type {
return struct {
data: *[max_size]u8,
len: usize,
ref_count: *usize,
allocator: std.mem.Allocator,
const Self = @This();
// TODO: 実装
pub fn init(allocator: std.mem.Allocator, str: []const u8) !Self {
// 実装
}
pub fn deinit(self: *Self) void {
// 実装: ref_countをデクリメント、0なら解放
}
pub fn clone(self: Self) !Self {
// 実装: ref_countをインクリメント
}
pub fn get(self: Self) []const u8 {
return self.data[0..self.len];
}
// TODO: 書き込み時にコピー
pub fn makeWritable(self: *Self) !void {
if (self.ref_count.* > 1) {
// コピーを作成
const new_data = try self.allocator.create([max_size]u8);
@memcpy(new_data[0..self.len], self.data[0..self.len]);
const new_ref = try self.allocator.create(usize);
new_ref.* = 1;
self.ref_count.* -= 1;
self.data = new_data;
self.ref_count = new_ref;
}
}
pub fn set(self: *Self, str: []const u8) !void {
try self.makeWritable();
@memcpy(self.data[0..str.len], str);
self.len = str.len;
}
};
}
pub fn main() !void {
const allocator = std.heap.page_allocator;
std.debug.print("=== Copy-on-Write String ===\n\n", .{});
var str1 = try CowString(128).init(allocator, "Hello");
defer str1.deinit();
std.debug.print("str1: {s}\n", .{str1.get()});
// クローン(データはコピーされない)
var str2 = try str1.clone();
defer str2.deinit();
std.debug.print("str2 (cloned): {s}\n", .{str2.get()});
// str2を変更(この時点でコピーされる)
try str2.set("World");
std.debug.print("str2 (modified): {s}\n", .{str2.get()});
std.debug.print("str1 (unchanged): {s}\n", .{str1.get()});
}
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: カスタムアロケータ実装 | 20点 |
| Part 2: メモリプール実装 | 20点 |
| Part 3: アリーナアロケータの活用 | 20点 |
| Part 4: メモリリーク検出 | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: スラブアロケータ | 10点 |
| Bonus 2: コピーオンライト | 10点 |
| **ボーナス合計** | **20点** |
合格基準
- マンダトリー: 64点以上で合格(80点満点の80%)
- ボーナス: 追加評価(最終成績の加算)
提出方法
exercise12/
├── part1/
│ └── custom_allocator.zig
├── part2/
│ └── memory_pool.zig
├── part3/
│ └── arena_usage.zig
├── part4/
│ └── leak_detection.zig
├── bonus1/
│ └── slab_allocator.zig
└── bonus2/
└── copy_on_write.zig
テスト実行
# マンダトリー
zig run part1/custom_allocator.zig
zig run part2/memory_pool.zig
zig run part3/arena_usage.zig
zig run part4/leak_detection.zig
# ボーナス
zig run bonus1/slab_allocator.zig
zig run bonus2/copy_on_write.zig