Skip to content

Commit

Permalink
Add allocation counts to tracking set
Browse files Browse the repository at this point in the history
  • Loading branch information
bens committed Mar 22, 2024
1 parent 244c3ab commit c290d45
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 74 deletions.
132 changes: 72 additions & 60 deletions util/runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
};

Expand Down Expand Up @@ -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,
} },
};
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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,
},
};
}
Expand All @@ -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);
},
};
}
Expand All @@ -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,
Expand All @@ -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.?);
}
15 changes: 11 additions & 4 deletions util/runner/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
7 changes: 7 additions & 0 deletions util/tracking_allocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 .{
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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)
Expand Down
29 changes: 19 additions & 10 deletions zbench.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
}
};
Expand Down Expand Up @@ -297,27 +301,32 @@ 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,
name: []const u8,
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.
Expand Down Expand Up @@ -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]", .{
Expand Down Expand Up @@ -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": {},
Expand All @@ -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),
},
);
Expand Down

0 comments on commit c290d45

Please sign in to comment.