第19章: デバッグとプロファイリング
学習目標
- std.debugモジュールを使ったデバッグ出力
- スタックトレースの読み方と活用
- アサーションとテスト駆動開発
- パフォーマンス計測とボトルネック特定
- メモリリーク検出と修正
std.debug - デバッグ機能
基本的なデバッグ出力
const std = @import("std");
pub fn basicDebug() void {
const value = 42;
const name = "Alice";
// 基本的な出力
std.debug.print("Value: {}\n", .{value});
std.debug.print("Name: {s}\n", .{name});
// 複数の値
std.debug.print("x={}, y={}, name={s}\n", .{ 10, 20, "Bob" });
// 任意の型を出力
const point = struct { x: i32, y: i32 }{ .x = 1, .y = 2 };
std.debug.print("Point: {any}\n", .{point});
}
アサーション
const std = @import("std");
pub fn assertions() void {
const x = 10;
// ランタイムアサーション
std.debug.assert(x == 10); // OK
// std.debug.assert(x == 20); // パニック
// カスタムメッセージ付きアサーション
if (x != 10) {
std.debug.panic("Expected x to be 10, got {}", .{x});
}
}
pub fn validateInput(value: i32) !void {
if (value < 0) {
return error.InvalidInput;
}
if (value > 100) {
return error.ValueTooLarge;
}
}
スタックトレース
const std = @import("std");
fn functionA() void {
functionB();
}
fn functionB() void {
functionC();
}
fn functionC() void {
// スタックトレースを出力
const trace = std.debug.getSelfDebugInfo() catch return;
const addr = @returnAddress();
std.debug.print("Stack trace from functionC:\n", .{});
std.debug.dumpStackTraceFromBase(addr, trace);
}
pub fn stackTraceExample() void {
functionA();
}
メモリデバッグ
const std = @import("std");
pub fn memoryDebugging() !void {
// GeneralPurposeAllocator はメモリリークを検出
var gpa = std.heap.GeneralPurposeAllocator(.{
.safety = true, // 安全性チェック有効
}){};
defer {
const leaked = gpa.deinit();
if (leaked == .leak) {
std.debug.print("Memory leak detected!\n", .{});
}
}
const allocator = gpa.allocator();
const data = try allocator.alloc(u8, 100);
// defer allocator.free(data); // これをコメントアウトするとリーク
_ = data;
allocator.free(data);
}
パフォーマンス計測
時間計測
const std = @import("std");
pub fn timeFunction(comptime func: anytype, args: anytype) !u64 {
const start = std.time.nanoTimestamp();
@call(.auto, func, args);
const end = std.time.nanoTimestamp();
return @intCast(end - start);
}
fn slowFunction() void {
var sum: u64 = 0;
var i: u64 = 0;
while (i < 1_000_000) : (i += 1) {
sum += i;
}
}
pub fn benchmarkExample() !void {
const ns = try timeFunction(slowFunction, .{});
const ms = ns / std.time.ns_per_ms;
std.debug.print("Execution time: {} ms\n", .{ms});
}
メモリ使用量追跡
const std = @import("std");
pub const TrackingAllocator = struct {
parent_allocator: std.mem.Allocator,
allocated: usize,
freed: usize,
peak: usize,
pub fn init(parent: std.mem.Allocator) TrackingAllocator {
return .{
.parent_allocator = parent,
.allocated = 0,
.freed = 0,
.peak = 0,
};
}
pub fn allocator(self: *TrackingAllocator) 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: *TrackingAllocator = @ptrCast(@alignCast(ctx));
const result = self.parent_allocator.rawAlloc(len, ptr_align, ret_addr);
if (result) |ptr| {
self.allocated += len;
self.peak = @max(self.peak, self.allocated - self.freed);
return ptr;
}
return null;
}
fn resize(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
new_len: usize,
ret_addr: usize,
) bool {
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
return self.parent_allocator.rawResize(buf, buf_align, new_len, ret_addr);
}
fn free(
ctx: *anyopaque,
buf: []u8,
buf_align: u8,
ret_addr: usize,
) void {
const self: *TrackingAllocator = @ptrCast(@alignCast(ctx));
self.freed += buf.len;
self.parent_allocator.rawFree(buf, buf_align, ret_addr);
}
pub fn report(self: TrackingAllocator) void {
std.debug.print("Memory Statistics:\n", .{});
std.debug.print(" Allocated: {} bytes\n", .{self.allocated});
std.debug.print(" Freed: {} bytes\n", .{self.freed});
std.debug.print(" Peak: {} bytes\n", .{self.peak});
std.debug.print(" Current: {} bytes\n", .{self.allocated - self.freed});
}
};
プロファイリング
CPU プロファイリング
const std = @import("std");
pub const Profiler = struct {
samples: std.ArrayList(Sample),
start_time: i64,
const Sample = struct {
name: []const u8,
duration_ns: u64,
};
pub fn init(allocator: std.mem.Allocator) Profiler {
return .{
.samples = std.ArrayList(Sample).init(allocator),
.start_time = std.time.nanoTimestamp(),
};
}
pub fn deinit(self: *Profiler) void {
self.samples.deinit();
}
pub fn measure(self: *Profiler, name: []const u8, func: anytype) !void {
const start = std.time.nanoTimestamp();
func();
const end = std.time.nanoTimestamp();
try self.samples.append(.{
.name = name,
.duration_ns = @intCast(end - start),
});
}
pub fn report(self: Profiler) void {
std.debug.print("\nProfile Report:\n", .{});
std.debug.print("{'─^40}\n", .{""});
for (self.samples.items) |sample| {
const ms = sample.duration_ns / std.time.ns_per_ms;
std.debug.print("{s:30} {:>8} ms\n", .{ sample.name, ms });
}
}
};
最適化テクニック
ビルドモード
# デバッグビルド(デフォルト)
zig build-exe main.zig
# リリースビルド(最適化)
zig build-exe main.zig -O ReleaseFast
# 小サイズ最適化
zig build-exe main.zig -O ReleaseSmall
# 安全性重視
zig build-exe main.zig -O ReleaseSafe
インライン化
const std = @import("std");
// インライン強制
inline fn add(a: i32, b: i32) i32 {
return a + b;
}
// インライン禁止
noinline fn complexFunction() void {
// 複雑な処理
}
まとめ
次の章では、実践プロジェクトに取り組みます。