pyk/bench
Tiny benchmarking library for Zig
Tiny benchmarking library for Zig.
const bench = @import("bench");
pub fn main() !void {
// Add your allocator here
// Run the benchmark, collect some metrics
const metrics = try bench.run(allocator, "Sleep", sleepWork, .{});
// Do anything with metrics here
// or you can simply report it to stdout
try bench.report({ .metrics = &.{metrics} });
}
fn sleepWork() !void {
var threaded = std.Io.Threaded.init(testing.allocator);
defer threaded.deinit();
const io = threaded.io();
try io.sleep(.fromMilliseconds(1), .awake);
std.mem.doNotOptimizeAway(io);
}
Default reporter looks like this:
Benchmark Summary: 3 benchmarks run
├─ NoOp 60ns 16.80M/s [baseline]
│ └─ cycles: 14 instructions: 36 ipc: 2.51 miss: 0
├─ Sleep 1.06ms 944/s 17648.20x slower
│ └─ cycles: 4.1k instructions: 2.9k ipc: 0.72 miss: 17
└─ Busy 32.38us 30.78K/s 539.68x slower
└─ cycles: 150.1k instructions: 700.1k ipc: 4.67 miss: 0
This tiny benchmark library support (✅) various metrics:
| Category | Metric | Description |
|---|---|---|
| Time | ✅ Mean / Average | Arithmetic average of all runs |
| Time | ✅ Median | The middle value (less sensitive to outliers) |
| Time | ✅ Min / Max | The absolute fastest and slowest runs |
| Time | CPU vs Wall Time | CPU time (active processing) vs Wall time (real world) |
| Throughput | ✅ Ops/sec | Operations per second |
| Throughput | ✅ Bytes/sec | Data throughput (MB/s, GB/s) |
| Throughput | Items/sec | Discrete items processed per second |
| Latency | Percentiles | p75, p99, p99.9. (e.g. "99% of requests were faster than X") |
| Latency | ✅ Std Dev / Variance | How much the results deviate from the average |
| Latency | Outliers | Detecting and reporting anomaly runs |
| Latency | Confidence / Margin of Error | e.g. "± 2.5%" |
| Latency | Histogram | Visual distribution of all runs |
| Memory | Bytes Allocated | Total heap memory requested per iteration |
| Memory | Allocation Count | Number of allocation calls |
| CPU | ✅ Cycles | CPU clock cycles used |
| CPU | ✅ Instructions | Total CPU instructions executed |
| CPU | ✅ IPC | Instructions Per Cycle (Efficiency) |
| CPU | ✅ Cache Misses | L1/L2 Cache misses |
| Comparative | ✅ Speedup (x) | "12.5x faster" (Current / Baseline). |
| Comparative | Relative Diff (%) | "+ 50%" or "- 10%". |
| Comparative | Big O | Complexity Analysis (O(n), O(log n)). |
| Comparative | R² (Goodness of Fit) | How well the data fits a linear model. |
Other metrics will be added as needed. Feel free to send a pull request.
Fetch latest version:
zig fetch --save=bench https://github.com/pyk/bench/archive/main.tar.gz
Add bench as a dependency to your build.zig.
If you are using it only for tests/benchmarks, it is recommended to mark it as lazy:
.dependencies = .{
.bench = .{
.url = "...",
.hash = "...",
.lazy = true, // here
},
}
This library is designed to show you "what", not "why". I recommend using a
proper profiling tool such as perf on linux + Firefox Profiler to answer
"why".
doNotOptimizeAway is your friend. For example if you are benchmarking some
scanner/tokenizer:
while (true) {
const token = try scanner.next();
if (token == .end) break;
total_ops += 1;
std.mem.doNotOptimizeAway(token); // CRITICAL
}
To get cycles, instructions, ipc (instructions per cycle) and
cache_misses metrics on Linux, you may need to enable the
kernel.perf_event_paranoid.
Install the Zig toolchain via mise (optional):
mise trust
mise install
Run tests:
zig build test --summary all
Build library:
zig build
Enable/disable kernel.perf_event_paranoid for debugging:
# Disable
sudo sysctl -w kernel.perf_event_paranoid=3
# Enable
sudo sysctl -w kernel.perf_event_paranoid=-1
MIT. Use it for whatever.