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

Migrate to std.time.Timer and tweak benchmark boundaries #19

Merged
merged 7 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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 @@ -45,7 +45,7 @@ pub fn build(b: *std.Build) void {

const example_step = b.step("test_examples", "Build examples");
// Add new examples here
for ([_][]const u8{ "basic", "bubble_sort" }) |example_name| {
for ([_][]const u8{ "basic", "bubble_sort", "sleep" }) |example_name| {
const example = b.addTest(.{
.name = example_name,
.root_source_file = .{ .path = b.fmt("examples/{s}.zig", .{example_name}) },
Expand Down
4 changes: 1 addition & 3 deletions examples/basic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ fn myBenchmark(_: *zbench.Benchmark) void {
}

test "bench test basic" {
var resultsAlloc = std.ArrayList(zbench.BenchmarkResult).init(test_allocator);
const resultsAlloc = std.ArrayList(zbench.BenchmarkResult).init(test_allocator);
var bench = try zbench.Benchmark.init("My Benchmark", test_allocator);

var benchmarkResults = zbench.BenchmarkResults{
.results = resultsAlloc,
};
defer benchmarkResults.results.deinit();

try zbench.run(myBenchmark, &bench, &benchmarkResults);
}
6 changes: 2 additions & 4 deletions examples/bubble_sort.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ fn bubbleSort(nums: []i32) void {
var j: usize = 0;
while (j < i) : (j += 1) {
if (nums[j] > nums[j + 1]) {
var tmp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = tmp;
std.mem.swap(i32, &nums[j], &nums[j + 1]);
}
}
}
Expand All @@ -23,7 +21,7 @@ fn myBenchmark(_: *zbench.Benchmark) void {
}

test "bench test bubbleSort" {
var resultsAlloc = std.ArrayList(zbench.BenchmarkResult).init(test_allocator);
const resultsAlloc = std.ArrayList(zbench.BenchmarkResult).init(test_allocator);
var bench = try zbench.Benchmark.init("Bubble Sort Benchmark", test_allocator);
var benchmarkResults = zbench.BenchmarkResults{
.results = resultsAlloc,
Expand Down
21 changes: 21 additions & 0 deletions examples/sleep.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const std = @import("std");
const zbench = @import("zbench");
const test_allocator = std.testing.allocator;

fn sooSleepy() void {
std.time.sleep(100_000_000);
}

fn sleepBenchmark(_: *zbench.Benchmark) void {
_ = sooSleepy();
}

test "bench test sleepy" {
const resultsAlloc = std.ArrayList(zbench.BenchmarkResult).init(test_allocator);
var bench = try zbench.Benchmark.init("Sleep Benchmark", test_allocator);
var benchmarkResults = zbench.BenchmarkResults{
.results = resultsAlloc,
};
defer benchmarkResults.results.deinit();
try zbench.run(sleepBenchmark, &bench, &benchmarkResults);
}
4 changes: 2 additions & 2 deletions util/timer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub const Timer = struct {

pub fn stop(self: *Timer) void {
if (self.startTime != 0) {
var stamp: u64 = @intCast(std.time.nanoTimestamp());
const stamp: u64 = @intCast(std.time.nanoTimestamp());
self.elapsedTime = stamp - self.startTime;
}
self.startTime = 0;
Expand All @@ -24,7 +24,7 @@ pub const Timer = struct {
if (self.startTime == 0) {
return self.elapsedTime;
} else {
var stamp: u64 = @intCast(std.time.nanoTimestamp());
const stamp: u64 = @intCast(std.time.nanoTimestamp());
return stamp - self.startTime;
}
}
Expand Down
84 changes: 36 additions & 48 deletions zbench.zig
Original file line number Diff line number Diff line change
@@ -1,48 +1,36 @@
const std = @import("std");
const c = @import("./util/color.zig");
const t = @import("./util/timer.zig");
const format = @import("./util/format.zig");

pub const Benchmark = struct {
name: []const u8,
N: usize = 1, // number of iterations
timer: t.Timer,
timer: std.time.Timer,
totalOperations: usize = 0,
minDuration: u64 = 18446744073709551615, // full 64 bits as a start
maxDuration: u64 = 0,
totalDuration: u64 = 0,
durations: std.ArrayList(u64),
allocator: std.mem.Allocator,
startTime: u64,

pub fn init(name: []const u8, allocator: std.mem.Allocator) !Benchmark {
var startTime: u64 = @intCast(std.time.nanoTimestamp());
if (startTime < 0) {
std.debug.warn("Failed to get start time. Defaulting to 0.\n", .{});
startTime = 0;
}

var bench = Benchmark{
const bench = Benchmark{
.name = name,
.allocator = allocator,
.timer = t.Timer{ .startTime = startTime },
.timer = std.time.Timer.start() catch return error.TimerUnsupported,
.durations = std.ArrayList(u64).init(allocator),
.startTime = startTime,
};
bench.timer.start();
return bench;
}

// Start the benchmark
pub fn start(self: *Benchmark) void {
self.timer.start();
self.startTime = self.timer.startTime;
self.timer.reset();
}

// Stop the benchmark and record the duration
pub fn stop(self: *Benchmark) void {
self.timer.stop();
const elapsedDuration = self.timer.elapsed();
const elapsedDuration = self.timer.read();
self.totalDuration += elapsedDuration;

if (elapsedDuration < self.minDuration) self.minDuration = elapsedDuration;
Expand All @@ -63,7 +51,11 @@ pub const Benchmark = struct {

// Function to get elapsed time since benchmark start
pub fn elapsed(self: *Benchmark) u64 {
return self.timer.elapsed();
var sum: u64 = 0;
for (self.durations.items) |duration| {
sum += duration;
}
return sum;
}

pub fn setTotalOperations(self: *Benchmark, ops: usize) void {
Expand Down Expand Up @@ -95,22 +87,13 @@ pub const Benchmark = struct {
var i = low;

var j = low;
while (j <= high) {
while (j <= high) : (j += 1) {
if (items[j] < pivot) {
// Manually swapping items[i] and items[j]
const temp = items[i];
items[i] = items[j];
items[j] = temp;
std.mem.swap(u64, &items[i], &items[j]);
i += 1;
}
j += 1;
}

// Swapping items[i] and items[high]
const temp = items[i];
items[i] = items[high];
items[high] = temp;

std.mem.swap(u64, &items[i], &items[high]);
return i;
}

Expand All @@ -119,10 +102,10 @@ pub const Benchmark = struct {
// quickSort might fail with an empty input slice, so safety checks first
const len = self.durations.items.len;
var lastIndex: usize = 0;
if (len > 0) {
if (len > 1) {
lastIndex = len - 1;
} else {
std.debug.print("Cannot calculate percentiles: empty durations list\n", .{});
std.debug.print("Cannot calculate percentiles: recorded less than two durations\n", .{});
return Percentiles{ .p75 = 0, .p99 = 0, .p995 = 0 };
}
quickSort(self.durations.items, 0, lastIndex - 1);
Expand Down Expand Up @@ -218,16 +201,16 @@ pub const BenchmarkResults = struct {

pub fn run(comptime func: BenchFunc, bench: *Benchmark, benchResult: *BenchmarkResults) !void {
defer bench.durations.deinit();
const MIN_DURATION = 1_000_000; // minimum benchmark time in nanoseconds (1 millisecond)
const MAX_N = 10000; // maximum number of executions for the final benchmark run
const MAX_ITERATIONS = 10; // Define a maximum number of iterations
const MIN_DURATION = 1_000_000_000; // minimum benchmark time in nanoseconds (1 second)
const MAX_N = 65536; // maximum number of executions for the final benchmark run

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for MAX_N and MAX_ITERATIONS to be so specific?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are powers of 2, no specific reason, sorry for the confusion ;-) The numbers are basically orders of magnitude where I found the benchmarks to run smoothly, and to not show too much variance.
I was thinking if we might give them reasonable defaults but expose as configurable, to find optimal settings for a specific scenario?

const MAX_ITERATIONS = 16384; // Define a maximum number of iterations

bench.N = 1; // initial value; will be updated...
var duration: u64 = 0;
var iterations: usize = 0; // Add an iterations counter
//var lastProgress: u8 = 0;
FObersteiner marked this conversation as resolved.
Show resolved Hide resolved

var lastProgress: u8 = 0;
// increase N until we've run for a sufficiently long enough time
// increase N until we've run for a sufficiently long time or exceeded max_iterations
while (duration < MIN_DURATION and iterations < MAX_ITERATIONS) {
bench.reset();

Expand All @@ -245,23 +228,28 @@ pub fn run(comptime func: BenchFunc, bench: *Benchmark, benchResult: *BenchmarkR
bench.N = MAX_N;
}

// Calculate the progress percentage
const pr = bench.N / MAX_N;
const progress = pr * 100;
// // Calculate the progress percentage
FObersteiner marked this conversation as resolved.
Show resolved Hide resolved
// const progress = bench.N * 100 / MAX_N;
//
// // Print the progress if it's a new percentage
// const currentProgress: u8 = @truncate(progress);
// if (currentProgress != lastProgress) {
// std.debug.print("Preparing...({}%)\n", .{currentProgress});
// lastProgress = currentProgress;
// }

// Print the progress if it's a new percentage
const currentProgress: u8 = @truncate(progress);
if (currentProgress != lastProgress) {
std.debug.print("Progress...({}%)\n", .{currentProgress});
lastProgress = currentProgress;
}
iterations += 1; // Increase the iteration counter
duration += bench.elapsed(); // ...and duration
}
// Get the elapsed time
duration = bench.elapsed();

// Safety first: make sure the recorded durations aren't all-zero
if (duration == 0) duration = 1;

// Adjust N based on the actual duration achieved
bench.N = @intCast((bench.N * MIN_DURATION) / duration);
// check that N doesn't go out of bounds
if (bench.N == 0) bench.N = 1;
if (bench.N > MAX_N) bench.N = MAX_N;

// Now run the benchmark with the adjusted N value
bench.reset();
Expand Down
Loading