diff --git a/util/runner.zig b/util/runner.zig index de583ee..2ef9cdc 100644 --- a/util/runner.zig +++ b/util/runner.zig @@ -48,8 +48,11 @@ const State = union(enum) { /// Array of timings collected. timings_ns: []u64, - /// Array of memory usages collected. - allocations: ?[]usize, + /// Array of maximum memory allocation readings. + allocation_maxes: ?[]usize, + + /// Array of counts of memory allocation readings. + allocation_counts: ?[]usize, }; }; @@ -78,10 +81,8 @@ pub fn init( .iterations_count = iterations, .iterations_remaining = iterations, .timings_ns = try allocator.alloc(u64, iterations), - .allocations = if (track_allocations) - try allocator.alloc(usize, iterations) - else - null, + .allocation_maxes = if (track_allocations) try allocator.alloc(usize, iterations) else null, + .allocation_counts = if (track_allocations) try allocator.alloc(usize, iterations) else null, } }, }; } @@ -113,10 +114,8 @@ pub fn next(self: *Runner, reading: Reading) Error!?Step { .iterations_count = N, .iterations_remaining = N, .timings_ns = try self.allocator.alloc(u64, N), - .allocations = if (self.track_allocations) - try self.allocator.alloc(usize, N) - else - null, + .allocation_maxes = if (self.track_allocations) try self.allocator.alloc(usize, N) else null, + .allocation_counts = if (self.track_allocations) try self.allocator.alloc(usize, N) else null, } }; } return .more; @@ -125,10 +124,16 @@ pub fn next(self: *Runner, reading: Reading) Error!?Step { if (0 < st.iterations_remaining) { const i = st.timings_ns.len - st.iterations_remaining; st.timings_ns[i] = reading.timing_ns; - if (st.allocations) |allocs| { - if (reading.max_allocated) |m| allocs[i] = m else { - self.allocator.free(allocs); - st.allocations = null; + if (st.allocation_maxes) |arr| { + if (reading.allocation_max) |m| arr[i] = m else { + self.allocator.free(arr); + st.allocation_maxes = null; + } + } + if (st.allocation_counts) |arr| { + if (reading.allocation_count) |m| arr[i] = m else { + self.allocator.free(arr); + st.allocation_counts = null; } } st.iterations_remaining -= 1; @@ -144,11 +149,13 @@ pub fn finish(self: *Runner) Error!Readings { return switch (self.state) { .preparing => .{ .timings_ns = &.{}, - .max_allocations = null, + .allocation_maxes = null, + .allocation_counts = null, }, .running => |st| .{ .timings_ns = st.timings_ns, - .max_allocations = st.allocations, + .allocation_maxes = st.allocation_maxes, + .allocation_counts = st.allocation_counts, }, }; } @@ -159,7 +166,8 @@ pub fn abort(self: *Runner) void { .preparing => {}, .running => |st| { self.allocator.free(st.timings_ns); - if (st.allocations) |allocs| self.allocator.free(allocs); + if (st.allocation_maxes) |arr| self.allocator.free(arr); + if (st.allocation_counts) |arr| self.allocator.free(arr); }, }; } @@ -186,30 +194,30 @@ test "Runner" { var r = try Runner.init(std.testing.allocator, 0, 16384, 2e9, false); { errdefer r.abort(); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - - try expectEq(Step.more, try r.next(Reading.init(100_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(400_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, null))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, null))); - try expectEq(@as(?Step, null), try r.next(Reading.init(400_000_000, null))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + + try expectEq(Step.more, try r.next(Reading.init(100_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(400_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, null, null))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, null, null))); + try expectEq(@as(?Step, null), try r.next(Reading.init(400_000_000, null, null))); } const result = try r.finish(); defer std.testing.allocator.free(result.timings_ns); - defer if (result.max_allocations) |m| std.testing.allocator.free(m); + defer if (result.allocation_maxes) |m| std.testing.allocator.free(m); try expectEqSlices(u64, &.{ 100_000_000, 200_000_000, 300_000_000, 400_000_000, 100_000_000, 200_000_000, 300_000_000, 400_000_000, @@ -220,34 +228,38 @@ test "Runner - memory tracking" { var r = try Runner.init(std.testing.allocator, 0, 16384, 2e9, true); { errdefer r.abort(); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, 256))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, 256))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, 512))); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, 512))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, 1024))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, 1024))); - try expectEq(Step.more, try r.next(Reading.init(100_000_000, 2048))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, 2045))); - try expectEq(Step.more, try r.next(Reading.init(300_000_000, 4096))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, 4096))); - try expectEq(Step.more, try r.next(Reading.init(200_000_000, 8192))); - - try expectEq(Step.more, try r.next(Reading.init(100, 1))); - try expectEq(Step.more, try r.next(Reading.init(200, 2))); - try expectEq(Step.more, try r.next(Reading.init(300, 4))); - try expectEq(Step.more, try r.next(Reading.init(400, 8))); - try expectEq(Step.more, try r.next(Reading.init(100, 16))); - try expectEq(Step.more, try r.next(Reading.init(200, 32))); - try expectEq(Step.more, try r.next(Reading.init(300, 64))); - try expectEq(@as(?Step, null), try r.next(Reading.init(400, 128))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, 256, 1))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, 256, 1))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, 512, 2))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, 512, 2))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, 1024, 4))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, 1024, 4))); + try expectEq(Step.more, try r.next(Reading.init(100_000_000, 2048, 8))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, 2045, 8))); + try expectEq(Step.more, try r.next(Reading.init(300_000_000, 4096, 16))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, 4096, 16))); + try expectEq(Step.more, try r.next(Reading.init(200_000_000, 8192, 32))); + + try expectEq(Step.more, try r.next(Reading.init(100, 1, 2))); + try expectEq(Step.more, try r.next(Reading.init(200, 2, 4))); + try expectEq(Step.more, try r.next(Reading.init(300, 4, 8))); + try expectEq(Step.more, try r.next(Reading.init(400, 8, 16))); + try expectEq(Step.more, try r.next(Reading.init(100, 16, 32))); + try expectEq(Step.more, try r.next(Reading.init(200, 32, 64))); + try expectEq(Step.more, try r.next(Reading.init(300, 64, 128))); + try expectEq(@as(?Step, null), try r.next(Reading.init(400, 128, 256))); } const result = try r.finish(); defer std.testing.allocator.free(result.timings_ns); - defer if (result.max_allocations) |m| std.testing.allocator.free(m); + defer if (result.allocation_maxes) |m| std.testing.allocator.free(m); + defer if (result.allocation_counts) |m| std.testing.allocator.free(m); try expectEqSlices(u64, &.{ 100, 200, 300, 400, 100, 200, 300, 400, }, result.timings_ns); try expectEqSlices(u64, &.{ 1, 2, 4, 8, 16, 32, 64, 128, - }, result.max_allocations.?); + }, result.allocation_maxes.?); + try expectEqSlices(u64, &.{ + 2, 4, 8, 16, 32, 64, 128, 256, + }, result.allocation_counts.?); } diff --git a/util/runner/types.zig b/util/runner/types.zig index 72f4fc9..4b63073 100644 --- a/util/runner/types.zig +++ b/util/runner/types.zig @@ -6,17 +6,24 @@ pub const Step = enum { more }; pub const Reading = struct { timing_ns: u64, - max_allocated: ?usize, + allocation_max: ?usize, + allocation_count: ?usize, - pub fn init(timing_ns: u64, max_allocated: ?usize) Reading { + pub fn init( + timing_ns: u64, + allocation_max: ?usize, + allocation_count: ?usize, + ) Reading { return .{ .timing_ns = timing_ns, - .max_allocated = max_allocated, + .allocation_max = allocation_max, + .allocation_count = allocation_count, }; } }; pub const Readings = struct { timings_ns: []u64, - max_allocations: ?[]usize, + allocation_maxes: ?[]usize, + allocation_counts: ?[]usize, }; diff --git a/util/tracking_allocator.zig b/util/tracking_allocator.zig index 6af83b6..9a43376 100644 --- a/util/tracking_allocator.zig +++ b/util/tracking_allocator.zig @@ -6,6 +6,7 @@ const TrackingAllocator = @This(); parent_allocator: Allocator, current_allocated: usize = 0, max_allocated: usize = 0, +allocation_count: usize = 0, pub fn init(parent_allocator: Allocator) TrackingAllocator { return .{ @@ -28,6 +29,10 @@ pub fn maxAllocated(self: TrackingAllocator) usize { return self.max_allocated; } +pub fn allocationCount(self: TrackingAllocator) usize { + return self.allocation_count; +} + fn alloc( ctx: *anyopaque, len: usize, @@ -37,6 +42,7 @@ fn alloc( const self: *TrackingAllocator = @ptrCast(@alignCast(ctx)); const result = self.parent_allocator.rawAlloc(len, log2_ptr_align, ra); if (result) |_| { + self.allocation_count += 1; self.current_allocated += len; if (self.max_allocated < self.current_allocated) self.max_allocated = self.current_allocated; @@ -54,6 +60,7 @@ fn resize( const self: *TrackingAllocator = @ptrCast(@alignCast(ctx)); const result = self.parent_allocator.rawResize(buf, log2_buf_align, new_len, ra); if (result) { + self.allocation_count += 1; if (buf.len < new_len) { self.current_allocated += new_len - buf.len; if (self.max_allocated < self.current_allocated) diff --git a/zbench.zig b/zbench.zig index dd4c251..d50646c 100644 --- a/zbench.zig +++ b/zbench.zig @@ -86,10 +86,14 @@ const Definition = struct { } return Runner.Reading{ .timing_ns = t.read(), - .max_allocated = if (self.config.track_allocations) + .allocation_max = if (self.config.track_allocations) tracking.maxAllocated() else null, + .allocation_count = if (self.config.track_allocations) + tracking.allocationCount() + else + null, }; } }; @@ -297,7 +301,8 @@ pub const Result = struct { allocator: std.mem.Allocator, name: []const u8, timings_ns: []const u64, - max_allocations: ?[]const usize, + allocation_maxes: ?[]const usize, + allocation_counts: ?[]const usize, pub fn init( allocator: std.mem.Allocator, @@ -305,19 +310,23 @@ pub const Result = struct { readings: Runner.Readings, ) !Result { std.sort.heap(u64, readings.timings_ns, {}, std.sort.asc(u64)); - if (readings.max_allocations) |allocs| - std.sort.heap(usize, allocs, {}, std.sort.asc(usize)); + if (readings.allocation_maxes) |arr| + std.sort.heap(usize, arr, {}, std.sort.asc(usize)); + if (readings.allocation_counts) |arr| + std.sort.heap(usize, arr, {}, std.sort.asc(usize)); return Result{ .allocator = allocator, .name = name, .timings_ns = readings.timings_ns, - .max_allocations = readings.max_allocations, + .allocation_maxes = readings.allocation_maxes, + .allocation_counts = readings.allocation_counts, }; } pub fn deinit(self: Result) void { self.allocator.free(self.timings_ns); - if (self.max_allocations) |allocs| self.allocator.free(allocs); + if (self.allocation_maxes) |arr| self.allocator.free(arr); + if (self.allocation_counts) |arr| self.allocator.free(arr); } /// Formats and prints the benchmark result in a human readable format. @@ -361,7 +370,7 @@ pub const Result = struct { try setColor(colors, writer, Color.reset); try writer.writeAll("\n"); - if (self.max_allocations) |allocs| { + if (self.allocation_maxes) |allocs| { const m = Statistics(usize).init(allocs); // Benchmark name const name = try std.fmt.bufPrint(&buf, "{s} [MEMORY]", .{ @@ -403,8 +412,8 @@ pub const Result = struct { pub fn writeJSON(self: Result, writer: anytype) !void { const timings_ns_stats = Statistics(u64).init(self.timings_ns); - if (self.max_allocations) |allocs| { - const max_allocated_stats = Statistics(usize).init(allocs); + if (self.allocation_maxes) |allocs| { + const allocation_maxes_stats = Statistics(usize).init(allocs); try writer.print( \\{{ "name": "{s}", \\ "timing_statistics": {}, "timings": {}, @@ -414,7 +423,7 @@ pub const Result = struct { std.fmt.fmtSliceEscapeLower(self.name), statistics.fmtJSON(u64, "nanoseconds", timings_ns_stats), format.fmtJSONArray(u64, self.timings_ns), - statistics.fmtJSON(usize, "bytes", max_allocated_stats), + statistics.fmtJSON(usize, "bytes", allocation_maxes_stats), format.fmtJSONArray(usize, allocs), }, );