diff --git a/build.zig b/build.zig index 10773f8..16b4b35 100644 --- a/build.zig +++ b/build.zig @@ -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}) }, diff --git a/examples/basic.zig b/examples/basic.zig index 90ad857..a6784c7 100644 --- a/examples/basic.zig +++ b/examples/basic.zig @@ -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); } diff --git a/examples/bubble_sort.zig b/examples/bubble_sort.zig index a07fa8b..54f77cc 100644 --- a/examples/bubble_sort.zig +++ b/examples/bubble_sort.zig @@ -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]); } } } @@ -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, diff --git a/examples/sleep.zig b/examples/sleep.zig new file mode 100644 index 0000000..7cfbf06 --- /dev/null +++ b/examples/sleep.zig @@ -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); +} diff --git a/util/timer.zig b/util/timer.zig index 476f8b6..4415b6e 100644 --- a/util/timer.zig +++ b/util/timer.zig @@ -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; @@ -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; } } diff --git a/zbench.zig b/zbench.zig index eae90dc..aa487db 100644 --- a/zbench.zig +++ b/zbench.zig @@ -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; @@ -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 { @@ -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; } @@ -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); @@ -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(); @@ -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();