第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 {
        // 複雑な処理
    }
    

    まとめ

  • std.debug モジュールでデバッグ出力
  • スタックトレースとアサーション
  • パフォーマンス計測とプロファイリング
  • メモリ使用量の追跡
  • 最適化オプション

次の章では、実践プロジェクトに取り組みます。