Very low-cost measuring of performance percentiles, for Zig
p99 is a lightweight, low-overhead library designed for generating real-time performance percentiles in high-frequency or latency-sensitive environments.
p99.Zig is the Zig implementation.
p99.Zig uses a fixed-size, zero-allocation logarithmic histogram with exactly 64 buckets.
Each bucket represents a power-of-two range of nanoseconds:
- Bucket
0represents[0, 1]nanoseconds; - Bucket
1represents[2, 3]nanoseconds; - Bucket
2represents[4, 7]nanoseconds; - ...
- Bucket
63represents[2^63, 2^64 - 1]nanoseconds.
Finding the bucket index is extremely fast and branchless, implemented using the CPU's count leading zeros instruction (@clz).
When querying percentiles (e.g., P50, P99), the library performs linear interpolation within the target bucket to approximate the duration with high accuracy.
The recommended way to add p99.Zig to your project is using the zig fetch command. Run the following in your project root:
zig fetch --save https://github.com/synesissoftware/p99.Zig/archive/refs/tags/v0.0.1.tar.gzThis will automatically download the package, compute its hash, and add it to your build.zig.zon dependencies:
.{
.name = .my_project,
.version = "0.1.0",
.dependencies = .{
.p99 = .{
.url = "https://github.com/synesissoftware/p99.Zig/archive/refs/tags/v0.0.1.tar.gz",
.hash = "1220...", // Automatically calculated by zig fetch
},
},
}Then, expose the dependency in your build.zig:
const p99_dep = b.dependency("p99", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("p99", p99_dep.module("p99"));p99.Zig supports the following compile-time build options:
-
binary-scaling(bool, default:false): Replaces integer division in the integer-based percentile methods (valueAtP90,valueAtP95,valueAtP99, etc.) with$2^{32}$ fixed-point binary scaling. Each percentile multiplier (e.g.,0.90for p90) is pre-encoded as au32constant and the target rank is computed via a single multiplication and a 32-bit right-shift, avoiding the cost of integer division entirely. This yields a significant speedup for percentile queries with a negligible loss of accuracy (the scaled multiplier differs from the true value by less than$10^{-9}$ ). The genericvalueAtPercentile(f64)method is unaffected by this feature.
To enable this option in your project, pass it when fetching the dependency in your build.zig:
const p99_dep = b.dependency("p99", .{
.target = target,
.optimize = optimize,
.@"binary-scaling" = true,
});Here is a minimal example demonstrating how to use p99.Zig:
const std = @import("std");
const p99 = @import("p99");
pub fn main(init: std.process.Init) !void {
var buffer: [4096]u8 = undefined;
var stdout_impl = std.Io.File.stdout().writer(init.io, &buffer);
const stdout = &stdout_impl.interface;
var h = p99.Histogram{};
// Push events (durations in nanoseconds)
_ = h.pushEventTimeNs(100);
_ = h.pushEventTimeNs(250);
_ = h.pushEventTimeNs(500);
_ = h.pushEventTimeNs(1000);
_ = h.pushEventTimeNs(5000);
// Query percentiles
const p50 = h.valueAtP50().?;
const p99_val = h.valueAtP99().?;
try stdout.print("P50: {d} ns\n", .{p50});
try stdout.print("P99: {d} ns\n", .{p99_val});
try stdout.flush();
}A benchmark suite is included in benches/benchmark_histogram.zig to measure performance.
To run the benchmarks in ReleaseFast mode (without binary scaling):
zig build bench -Doptimize=ReleaseFastTo run the benchmarks with the binary-scaling optimization enabled:
zig build bench -Dbinary-scaling=true -Doptimize=ReleaseFastDefect reports, feature requests, and pull requests are welcome on https://github.com/synesissoftware/p99.Zig.
p99.Zig has no (non-development) dependencies beyond the Zig standard library.
p99.Zig has no development dependencies beyond the Zig standard library.
Other implementations of the p99 specification include:
p99.Zig is released under the 3-clause BSD license. See LICENSE for details.