Skip to content

Commit

Permalink
docs: add hooks documentation and example (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
AxelDelsol authored Sep 4, 2024
1 parent 9fa246b commit 0c595ab
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn setupExamples(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.
const example_names = [_][]const u8{
"basic",
"bubble_sort",
"bubble_sort_hooks",
"hooks",
"json",
"memory_tracking",
Expand Down
69 changes: 69 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Hooks

This guide explains what lifecycle hooks are and how to use them.

## Concepts

Lifecycle hooks provide control over a benchmark environment. A hook is a function with the following signature: `fn () void`. Its execution is not included in the benchmark reports.

There are 4 kinds of hooks summarised in the following table:

| Hook | When is it called ? | Goal/Actions | Example(s) |
|---------------|----------------------------------------|--------------------|---------------------------------------------------------|
| `before_all` | Executed at the start of the benchmark | Global setup | Allocate memory, initialize variables for the benchmark |
| `before_each` | Executed before each iteration | Iteration setup | Setup/Allocate benchmark data |
| `after_each` | Executed after each iteration | Iteration teardown | Reset/Free benchmark data |
| `after_all` | Executed at the end of the benchmark | Global teardown | Free memory, deinit variables |

## Usage

zBench provides two ways to register hooks: globally or for a given benchmark.

---

Global registration adds hooks to each added benchmark.

```zig
test "bench test hooks" {
const stdout = std.io.getStdOut().writer();
var bench = zbench.Benchmark.init(std.testing.allocator, .{ .hooks = .{
.before_all = beforeAllHook,
.after_all = afterAllHook,
} });
defer bench.deinit();
try bench.add("Benchmark 1 ", myBenchmark, .{});
try bench.add("Benchmark 2 ", myBenchmark, .{});
try stdout.writeAll("\n");
try bench.run(stdout);
}
```

In this example, both Benchmark 1 and Benchmark 2 will execute `beforeAllHook` and `afterAllHook`. Note that `before_each` and `after_each` can be omitted because hooks are optional.

---

Hooks can also be included with the `add` and `addParam` methods.

```zig
test "bench test hooks" {
const stdout = std.io.getStdOut().writer();
var bench = zbench.Benchmark.init(std.testing.allocator, .{});
defer bench.deinit();
try bench.add("Benchmark 1", myBenchmark, .{
.hooks = .{
.before_all = beforeAllHook,
.after_all = afterAllHook,
},
});
try bench.add("Benchmark 2", myBenchmark, .{});
try stdout.writeAll("\n");
try bench.run(stdout);
}
```

In this example, only Benchmark 1 will execute `beforeAllHook` and `afterAllHook`.
99 changes: 99 additions & 0 deletions examples/bubble_sort_hooks.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// This example shows how to use hooks to provide more control over a benchmark.
// The bubble_sort.zig example is enhanced with randomly generated numbers.
// Global strategy:
// * At the start of the benchmark, i.e., before the first iteration, we allocate an ArrayList and setup a random number generator.
// * Before each iteration, we fill the ArrayList with random numbers.
// * After each iteration, we reset the ArrayList while keeping the allocated memory.
// * At the end of the benchmark, we deinit the ArrayList.
const std = @import("std");
const inc = @import("include");
const zbench = @import("zbench");

// Global variables modified/accessed by the hooks.
const test_allocator = std.testing.allocator;
const array_size: usize = 100;
// BenchmarkData contains the data generation logic.
var benchmark_data: BenchmarkData = undefined;

// Hooks do not accept any parameters and cannot return anything.
fn beforeAll() void {
benchmark_data.init(test_allocator, array_size) catch unreachable;
}

fn beforeEach() void {
benchmark_data.fill();
}

fn myBenchmark(_: std.mem.Allocator) void {
bubbleSort(benchmark_data.numbers.items);
}

fn bubbleSort(nums: []i32) void {
var i: usize = nums.len - 1;
while (i > 0) : (i -= 1) {
var j: usize = 0;
while (j < i) : (j += 1) {
if (nums[j] > nums[j + 1]) {
std.mem.swap(i32, &nums[j], &nums[j + 1]);
}
}
}
}

fn afterEach() void {
benchmark_data.reset();
}

fn afterAll() void {
benchmark_data.deinit();
}

test "bench test bubbleSort with hooks" {
const stdout = std.io.getStdOut().writer();

var bench = zbench.Benchmark.init(test_allocator, .{});
defer bench.deinit();

try bench.add("Bubble Sort Benchmark", myBenchmark, .{
.track_allocations = true, // Option used to show that hooks are not included in the tracking.
.hooks = .{ // Fields are optional and can be omitted.
.before_all = beforeAll,
.after_all = afterAll,
.before_each = beforeEach,
.after_each = afterEach,
},
});

try stdout.writeAll("\n");
try bench.run(stdout);
}

const BenchmarkData = struct {
rand: std.Random,
numbers: std.ArrayList(i32),
prng: std.Random.DefaultPrng,

pub fn init(self: *BenchmarkData, allocator: std.mem.Allocator, num: usize) !void {
self.prng = std.rand.DefaultPrng.init(blk: {
var seed: u64 = undefined;
std.posix.getrandom(std.mem.asBytes(&seed)) catch unreachable;
break :blk seed;
});
self.rand = self.prng.random();
self.numbers = try std.ArrayList(i32).initCapacity(allocator, num);
}

pub fn deinit(self: BenchmarkData) void {
self.numbers.deinit();
}

pub fn fill(self: *BenchmarkData) void {
for (0..self.numbers.capacity) |_| {
self.numbers.appendAssumeCapacity(self.rand.intRangeAtMost(i32, 0, 100));
}
}

pub fn reset(self: *BenchmarkData) void {
self.numbers.clearRetainingCapacity();
}
};

0 comments on commit 0c595ab

Please sign in to comment.