-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add hooks documentation and example (#89)
- Loading branch information
1 parent
9fa246b
commit 0c595ab
Showing
3 changed files
with
169 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
}; |