Skip to content

Commit

Permalink
refactor: migrate to std.time.Timer and adjust benchmark boundaries #19
Browse files Browse the repository at this point in the history
- Replace existing time measurement implementation with std.time.Timer for improved accuracy and simplicity.
- Adjust the benchmark boundaries to cater to more accurate time measurements and ensure consistency across different platforms.
- Refine the logic for setting the minimum and maximum duration of benchmarks to better reflect real-world scenarios.
  • Loading branch information
hendriknielaender authored Nov 22, 2023
2 parents 0bfd057 + e3db52a commit 3e48ed1
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 59 deletions.
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
75 changes: 26 additions & 49 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,15 @@ 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
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;
// 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 +227,18 @@ 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;

// 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

0 comments on commit 3e48ed1

Please sign in to comment.