diff --git a/build.zig b/build.zig index 896c4bf..105abd6 100644 --- a/build.zig +++ b/build.zig @@ -60,6 +60,7 @@ fn setupExamples(b: *std.Build, target: std.zig.CrossTarget, optimize: std.built const example_names = [_][]const u8{ "basic", "bubble_sort", + "parameterised", "sleep", }; diff --git a/examples/parameterised.zig b/examples/parameterised.zig new file mode 100644 index 0000000..d04ac8e --- /dev/null +++ b/examples/parameterised.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +const zbench = @import("zbench"); +const test_allocator = std.testing.allocator; + +const MyBenchmark = struct { + loops: usize, + + fn init(loops: usize) MyBenchmark { + return .{ .loops = loops }; + } + + pub fn run(self: MyBenchmark, _: std.mem.Allocator) void { + var result: usize = 0; + for (0..self.loops) |i| result += i * i; + } +}; + +test "bench test parameterised" { + const stdout = std.io.getStdOut().writer(); + var bench = zbench.Benchmark.init(test_allocator, .{}); + defer bench.deinit(); + + try bench.addParam("My Benchmark 1", &MyBenchmark.init(100_000), .{}); + try bench.addParam("My Benchmark 2", &MyBenchmark.init(200_000), .{}); + + const results = try bench.run(); + defer results.deinit(); + try stdout.writeAll("\n"); + try results.prettyPrint(stdout, true); +} diff --git a/zbench.zig b/zbench.zig index 660a6be..216f450 100644 --- a/zbench.zig +++ b/zbench.zig @@ -51,13 +51,22 @@ pub const Config = struct { /// A benchmark definition. const Definition = struct { name: []const u8, - func: BenchFunc, config: Config, + defn: union(enum) { + simple: BenchFunc, + parameterised: struct { + func: ParameterisedFunc, + context: *const anyopaque, + }, + }, }; /// A function pointer type that represents a benchmark function. pub const BenchFunc = *const fn (std.mem.Allocator) void; +/// A function pointer type that represents a parameterised benchmark function. +pub const ParameterisedFunc = *const fn (*const anyopaque, std.mem.Allocator) void; + /// Benchmark manager, add your benchmark functions and run measure them. pub const Benchmark = struct { allocator: std.mem.Allocator, @@ -84,7 +93,37 @@ pub const Benchmark = struct { ) !void { try self.benchmarks.append(self.allocator, Definition{ .name = name, - .func = func, + .defn = .{ .simple = func }, + .config = optional(Config, config, self.common_config), + }); + } + + /// Add a benchmark function to be timed with `run()`. + pub fn addParam( + self: *Benchmark, + name: []const u8, + benchmark: anytype, + config: Optional(Config), + ) !void { + // Check the benchmark parameter is the proper type. + const T: type = switch (@typeInfo(@TypeOf(benchmark))) { + .Pointer => |ptr| if (ptr.is_const) ptr.child else @compileError( + "benchmark must be a const ptr to a struct with a 'run' method", + ), + else => @compileError( + "benchmark must be a const ptr to a struct with a 'run' method", + ), + }; + + // Check the benchmark parameter has a well typed run function. + _ = @as(fn (T, std.mem.Allocator) void, T.run); + + try self.benchmarks.append(self.allocator, Definition{ + .name = name, + .defn = .{ .parameterised = .{ + .func = @ptrCast(&T.run), + .context = @ptrCast(benchmark), + } }, .config = optional(Config, config, self.common_config), }); } @@ -117,7 +156,10 @@ pub const Benchmark = struct { defer if (defn.config.hooks.after_each) |hook| hook(); var t = try std.time.Timer.start(); - defn.func(self.allocator); + switch (defn.defn) { + .simple => |func| func(self.allocator), + .parameterised => |x| x.func(@ptrCast(x.context), self.allocator), + } return t.read(); }