Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add standard-deviation metric #30

Merged
merged 1 commit into from
Jan 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn setupTesting(b: *std.Build, target: std.zig.CrossTarget, optimize: std.builti
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);

const test_dirs = [_][]const u8{"util"};
const test_dirs = [_][]const u8{"util", "."};
for (test_dirs) |dir| {
addTestsFromDir(b, test_step, dir, target, optimize);
}
Expand Down
26 changes: 26 additions & 0 deletions tests.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const std = @import("std");
const test_alloc = std.testing.allocator;
const print = std.debug.print;
const expectEq = std.testing.expectEqual;

const Benchmark = @import("./zbench.zig").Benchmark;

test "Benchmark.calculateStd and Benchmark.calculateAverage" {
var bench = try Benchmark.init("test_bench", std.testing.allocator);
defer bench.durations.deinit();

try expectEq(@as(u64, 0), bench.calculateAverage());
try expectEq(@as(u64, 0), bench.calculateStd());

try bench.durations.append(0);
try expectEq(@as(u64, 0), bench.calculateAverage());
try expectEq(@as(u64, 0), bench.calculateStd());

for (1..16) |i| try bench.durations.append(i);
try expectEq(@as(u64, 7), bench.calculateAverage());
try expectEq(@as(u64, 4), bench.calculateStd());

for (16..101) |i| try bench.durations.append(i);
try expectEq(@as(u64, 50), bench.calculateAverage());
try expectEq(@as(u64, 29), bench.calculateStd());
}
30 changes: 26 additions & 4 deletions zbench.zig
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,21 @@ pub const Benchmark = struct {
var p995_buffer: [128]u8 = undefined;
const p995_str = try format.duration(p995_buffer[0..], percentiles.p995);

var avg_buffer: [128]u8 = undefined;
const avg_str = try format.duration(avg_buffer[0..], self.calculateAverage());
var avg_std_buffer: [128]u8 = undefined;
var avg_std_offset = (try format.duration(avg_std_buffer[0..], self.calculateAverage())).len;
avg_std_offset += (try std.fmt.bufPrint(avg_std_buffer[avg_std_offset..], " ± ", .{})).len;
avg_std_offset += (try format.duration(avg_std_buffer[avg_std_offset..], self.calculateStd())).len;
const avg_std_str = avg_std_buffer[0..avg_std_offset];

var min_buffer: [128]u8 = undefined;
const min_str = try format.duration(min_buffer[0..], self.minDuration);

var max_buffer: [128]u8 = undefined;
const max_str = try format.duration(max_buffer[0..], self.maxDuration);

std.debug.print("{s:<20} {s:<12} {s:<20} {s:<10} {s:<10} {s:<10}\n", .{ "benchmark", "time (avg)", "(min ... max)", "p75", "p99", "p995" });
std.debug.print("{s:<20} {s:<12} {s:<20} {s:<10} {s:<10} {s:<10}\n", .{ "benchmark", "time (avg ± σ)", "(min ... max)", "p75", "p99", "p995" });
std.debug.print("--------------------------------------------------------------------------------------\n", .{});
std.debug.print("{s:<20} \x1b[33m{s:<12}\x1b[0m (\x1b[94m{s}\x1b[0m ... \x1b[95m{s}\x1b[0m) \x1b[90m{s:<10}\x1b[0m \x1b[90m{s:<10}\x1b[0m \x1b[90m{s:<10}\x1b[0m\n", .{ self.name, avg_str, min_str, max_str, p75_str, p99_str, p995_str });
std.debug.print("{s:<20} \x1b[33m{s:<12}\x1b[0m (\x1b[94m{s}\x1b[0m ... \x1b[95m{s}\x1b[0m) \x1b[90m{s:<10}\x1b[0m \x1b[90m{s:<10}\x1b[0m \x1b[90m{s:<10}\x1b[0m\n", .{ self.name, avg_std_str, min_str, max_str, p75_str, p99_str, p995_str });
}

/// Calculate the average duration
Expand All @@ -183,6 +186,25 @@ pub const Benchmark = struct {

return avg;
}

/// Calculate the standard deviation of the durations
pub fn calculateStd(self: Benchmark) u64 {
if (self.durations.items.len <= 1) return 0;

const avg = self.calculateAverage();
var nvar: u64 = 0;
for (self.durations.items) |dur| {
// NOTE: With realistic real-life samples this will never overflow,
// however a solution without bitcasts would still be cleaner
const d: i64 = @bitCast(dur);
const a: i64 = @bitCast(avg);

nvar += @bitCast((d - a) * (d - a));
}

// We are using the non-biased estimator for the variance; sum(X - μ)^2 / (n - 1)
return std.math.sqrt(nvar / (self.durations.items.len - 1));
}
};

/// BenchFunc is a function type that represents a benchmark function.
Expand Down
Loading