p99.Rust
Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.).
Table of Contents
- Introduction
- How It Works
- Performance & Trade-offs
- Installation
- Components
- Examples
- Project Information
Introduction
p99 is a lightweight, low-overhead library designed for generating real-time performance percentiles in high-frequency or latency-sensitive environments.
p99.Rust is the Rust implementation.
How It Works
Histogram is a low-overhead, zero-allocation, fixed-size structure designed to track event durations (typically in nanoseconds) using 64 logarithmic buckets.
- Logarithmic Bucketing: The bucket boundaries are spaced as powers of two:
- Bucket
0represents[0, 1]nanoseconds; - Bucket
1represents[2, 3]nanoseconds; - Bucket
2represents[4, 7]nanoseconds; - Bucket
irepresents[2^i, 2^(i+1) - 1]nanoseconds.
- Bucket
- Branchless Indexing: Finding the correct bucket index for an incoming duration is extremely fast and branchless. It is computed in a few CPU instructions using the CPU's leading-zeros count intrinsic (
u64::leading_zeros). - Linear Interpolation: Percentile queries iterate through the buckets to find the target rank and perform linear interpolation within the matching bucket to approximate the exact percentile duration.
Performance & Trade-offs
Performance Claims
- Zero Allocation:
Histogramdoes not allocate memory on the heap during creation, event insertion, or percentile queries. It is a compact (~576-byte) structure that can reside entirely on the stack or be embedded in other structures. - Low-Latency Operations: The fixed bucket layout is designed for low-latency insertion and percentile queries. Actual timings depend on the processor, compiler, build profile, and workload.
- Instruction-Cache Friendly: The query methods are designed with a "thin caller / heavy worker" pattern to prevent instruction-cache bloat and maintain high CPU cache locality under real-world workloads.
The statements above describe implementation characteristics or design goals, not guaranteed timings. Measured results from the checked-in Criterion benchmark are reported below.
Trade-offs & Sacrifices
- Logarithmic Precision: To achieve zero allocation and constant-time operations,
Histogramsacrifices exact precision. It does not store individual event times. Instead, values are grouped into logarithmic buckets. - Approximation: Percentile values are approximated using linear interpolation within the bucket boundaries. For very large values, the bucket width is wider, which leads to a wider approximation range. However, for low-latency performance measurements where precision is needed most (the lower nanosecond ranges), the buckets are extremely narrow (e.g., 1ns, 2ns, 4ns wide), providing exceptional resolution.
binary-scalingAccuracy: When thebinary-scalingfeature is enabled, the percentile target rank is computed using a $2{32}$ fixed-point approximation. The pre-encoded multiplier for each percentile (e.g.,3_865_470_566 >> 32≈0.9000for p90) differs from the true decimal value by less than $10{-9}$, which is far below the approximation error introduced by the logarithmic bucketing itself. In practice this has no measurable impact on percentile accuracy.
Installation
Reference the current release in Cargo.toml in the usual way:
= { = "0.0.4" }
To enable the optional binary-scaling optimization:
= { = "0.0.4", = ["binary-scaling"] }
Components
Constants
No public constants are defined at this time.
Enumerations
No public enumerations are defined at this time.
Features
The following crate features are available:
-
"binary-scaling"(opt-in): Replaces integer division in the integer-based percentile methods (#value_at_p90(),#value_at_p95(),#value_at_p99(), 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 ~1.5x to 2x speedup for percentile queries with a negligible loss of accuracy (the scaled multiplier differs from the true value by less than $10{-9}$). The generic#value_at_percentile(f64)method is unaffected by this feature; -
"null-feature"(opt-in): A no-op feature that has no effect on the compiled library. It exists to simplify driver scripts and CI pipelines that conditionally pass--featuresflags, allowing a feature list to always be present even when no real features are needed;
Enabling binary-scaling
Add the feature in your Cargo.toml:
[]
= { = "0.0.4", = ["binary-scaling"] }
Or, when building from the command line:
# Default (standard integer division)
# With binary scaling enabled
Benchmark Results
Measured with criterion on
100,000 events per workload, using the release profile on an Apple M-series
machine. Criterion's default measurement configuration was used. The exact
machine, operating-system, compiler, and Criterion output are not recorded,
so these results are illustrative rather than guaranteed. Only the
integer-based percentile methods are affected; the generic
value_at_percentile(f64) method is unchanged.
Run the benchmark in the release profile with
cargo bench --bench histogram --release, then repeat with
--features binary-scaling to reproduce the comparison.
| Method | Default | binary-scaling |
Improvement |
|---|---|---|---|
value_at_p90() |
23.25 ns | 21.63 ns | -7.0% |
value_at_p99() (dense) |
16.52 ns | 14.88 ns | -10.4% |
value_at_p99() (wide) |
23.39 ns | 21.55 ns | -7.9% |
value_at_p99_99() |
23.32 ns | 21.64 ns | -7.2% |
Methods using simple fractional multipliers (p50 = 1/2, p75 = 3/4) already compile to bit-shifts without this feature, so they show no change.
Functions
No public functions are defined at this time.
Macros
No public macros are defined at this time.
Structures
The following public structures are defined in the current version:
Histogram
A low-cost, zero-allocation, 64-bucket logarithmic histogram designed for recording event durations in nanoseconds and querying high-resolution percentiles.
Definition
Minimal Example
Here is a simple example of how to initialize a Histogram, record event times, and query percentiles:
use Histogram;
use Duration;
Traits
No public traits are defined at this time.
Examples
An example program showing Histogram usage is provided in examples/build_histogram.rs.
It simulates a histogram of event times generated by std::thread::sleep delays under a custom PRNG.
The number of iterations can be configured via the P99_TRIES environment variable:
# Run with the default of 100 tries
# Run with 1000 tries
P99_TRIES=1000
# Run with binary-scaling enabled (faster percentile queries)
Project Information
Where to get help
Contribution guidelines
Defect reports, feature requests, and pull requests are welcome on https://github.com/synesissoftware/p99.Rust.
Dependencies
p99.Rust has no (non-development) dependencies.
Development Dependencies
Crates upon which p99.Rust has development dependencies:
Cargo.lock is retained so local and CI validation use a reproducible
dependency graph. Commands that consume the lockfile use --locked.
License
p99.Rust is released under the 3-clause BSD license. See LICENSE for details.