Skip to content
 
 

Repository files navigation

p99.Rust

Language License GitHub release Last Commit Crates.io

Low-cost generation of performance percentiles (p50, p90, p99, p99.9, etc.).

Table of Contents

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 0 represents [0, 1] nanoseconds;
    • Bucket 1 represents [2, 3] nanoseconds;
    • Bucket 2 represents [4, 7] nanoseconds;
    • Bucket i represents [2^i, 2^(i+1) - 1] nanoseconds.
  • 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: Histogram does 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.
  • Ultra-Low Latency Insertion: Recording a latency measurement (push_event_time_ns) takes approximately 11 nanoseconds (about 35 CPU cycles on modern hardware).
  • Blazing-Fast Queries: Querying percentiles (such as value_at_p99()) takes only 11 to 17 nanoseconds, depending on the distribution of events across the buckets.
  • 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.

Trade-offs & Sacrifices

  • Logarithmic Precision: To achieve zero allocation and constant-time operations, Histogram sacrifices 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-scaling Accuracy: When the binary-scaling feature 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 >> 320.9000 for 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 in Cargo.toml in the usual way:

p99 = { version = "0" }

To enable the optional binary-scaling optimization:

p99 = { version = "0", features = ["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.90 for p90) is pre-encoded as a u32 constant 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 --features flags, 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:

[dependencies]
p99 = { version = "0", features = ["binary-scaling"] }

Or, when building from the command line:

# Default (standard integer division)
cargo run --example build_histogram

# With binary scaling enabled
cargo run --example build_histogram --features binary-scaling

Benchmark Results

Measured with criterion on 100k events (Apple M-series, release profile). Only the integer-based percentile methods are affected; the generic value_at_percentile(f64) method is unchanged.

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
pub struct Histogram {
    // ... private fields ...
}
Minimal Example

Here is a simple example of how to initialize a Histogram, record event times, and query percentiles:

use p99::Histogram;
use std::time::Duration;

fn main() {
    // 1. Initialize a default histogram (zero-allocated, ~576 bytes on the stack)
    let mut histogram = Histogram::default();

    // 2. Record event times using various units
    histogram.push_event_time_ns(150); // 150 ns
    histogram.push_event_time_us(5);   // 5 microseconds (5,000 ns)
    histogram.push_event_time_ms(10);  // 10 milliseconds (10,000,000 ns)
    histogram.push_event_duration(Duration::from_nanos(250));

    // 3. Retrieve basic statistics
    assert_eq!(histogram.event_count(), 4);
    assert_eq!(histogram.min_event_time(), Some(150));
    assert_eq!(histogram.max_event_time(), Some(10_000_000));
    assert_eq!(histogram.event_time_total(), Some(10_005_400));

    // 4. Query percentiles
    // Generic floating-point query (e.g., 90th percentile)
    if let Some(p90) = histogram.value_at_percentile(90.0) {
        println!("p90: {} ns", p90);
    }

    // Fast, optimized integer-based percentile wrappers
    if let Some(p50) = histogram.value_at_p50() {
        println!("p50 (median): {} ns", p50);
    }
    if let Some(p99) = histogram.value_at_p99() {
        println!("p99: {} ns", p99);
    }
}

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
cargo run --example build_histogram

# Run with 1000 tries
P99_TRIES=1000 cargo run --example build_histogram

# Run with binary-scaling enabled (faster percentile queries)
cargo run --example build_histogram --features binary-scaling

Project Information

Where to get help

GitHub Page

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.

Dev Dependencies

Crates upon which p99.Rust has development dependencies:

License

p99.Rust is released under the 3-clause BSD license. See LICENSE for details.

About

Very low-cost measuring of performance percentiles, for Rust

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages