diff --git a/bench.zig b/bench.zig index 1754432..888dc05 100644 --- a/bench.zig +++ b/bench.zig @@ -9,12 +9,32 @@ const time = std.time; const Decl = std.builtin.TypeInfo.Declaration; -pub fn benchmark(comptime B: type) !void { +pub const Opts = struct { + min_iterations: usize = 10000, + max_iterations: usize = 100000, + max_time: usize = 500 * time.ns_per_ms, +}; + +pub fn benchmark(type_or_instance: anytype) !void { + switch (@typeInfo(@TypeOf(type_or_instance))) { + .Type => try run(type_or_instance, type_or_instance, null), + .Pointer => |p| try run(p.child, type_or_instance, null), + else => @compileError("Pass a type or a pointer/instance to a struct."), + } +} + +pub fn run(comptime B: type, context: anytype, run_opts: ?Opts) !void { const args = if (@hasDecl(B, "args")) B.args else [_]void{{}}; const arg_names = if (@hasDecl(B, "arg_names")) B.arg_names else [_]u8{}; - const min_iterations = if (@hasDecl(B, "min_iterations")) B.min_iterations else 10000; - const max_iterations = if (@hasDecl(B, "max_iterations")) B.max_iterations else 100000; - const max_time = 500 * time.ns_per_ms; + + var opts = Opts{}; + if (run_opts) |ro| { + opts = ro; + } else { + if (@hasDecl(B, "min_iterations")) opts.min_iterations = B.min_iterations; + if (@hasDecl(B, "max_iterations")) opts.max_iterations = B.max_iterations; + } + opts.max_iterations = math.max(opts.min_iterations, opts.max_iterations); const functions = comptime blk: { var res: []const Decl = &[_]Decl{}; @@ -78,14 +98,14 @@ pub fn benchmark(comptime B: type) !void { var runtime_sum: u128 = 0; var i: usize = 0; - while (i < min_iterations or - (i < max_iterations and runtime_sum < max_time)) : (i += 1) + while (i < opts.min_iterations or + (i < opts.max_iterations and runtime_sum < opts.max_time)) : (i += 1) { timer.reset(); const res = switch (@TypeOf(arg)) { - void => @field(B, def.name)(), - else => @field(B, def.name)(arg), + void => @field(context, def.name)(), + else => @field(context, def.name)(arg), }; const runtime = timer.read(); runtime_sum += runtime; @@ -244,3 +264,60 @@ test "benchmark generics" { } }); } + +test "benchmark instance" { + const iterations: usize = 100000; + const args = [_][]const u8{ "01" }; + const arg_names = [_][]const u8{ "01 x n" }; + + const capacity = args[0].len * iterations; + const BoundedArray = std.BoundedArray(u8, capacity); + const ArrayList = std.ArrayList(u8); + const allocator = std.testing.allocator; + + var instance = (struct { + const args = args; + const arg_names = arg_names; + const min_iterations = iterations; + const max_iterations = iterations; + + const Self = @This(); + bounded_array: BoundedArray, + non_resizing_list: ArrayList, + resizing_list: ArrayList, + + fn bounded_array_append_slice(self: *Self, data: []const u8) !void { + try self.bounded_array.appendSlice(data); + } + fn non_resizing_list_append_slice(self: *Self, data: []const u8) !void { + try self.non_resizing_list.appendSlice(data); + } + fn resizing_list_append_slice(self: *Self, data: []const u8) !void { + try self.resizing_list.appendSlice(data); + } + } { + .bounded_array = .{ .buffer = undefined, .len = 0 }, + .non_resizing_list = ArrayList.init(allocator), + .resizing_list = ArrayList.init(allocator), + }); + defer { + instance.non_resizing_list.deinit(); + instance.resizing_list.deinit(); + } + + try instance.non_resizing_list.ensureTotalCapacity(capacity); + + try benchmark(&instance); + + // reset + instance.bounded_array.len = 0; + instance.non_resizing_list.clearRetainingCapacity(); + instance.resizing_list.clearAndFree(); + + // run with fewer iterations + const fewer_iterations = iterations / 2; + try run(@TypeOf(instance), &instance, .{ + .min_iterations = fewer_iterations, + .max_iterations = fewer_iterations, + }); +}