An allocation-free, compile-time table-lookup based implementation of the OpenTelemetry Exponential Histogram in Rust.
Exponential histograms provide a compact, high-resolution representation of value distributions using logarithmically-spaced bucket boundaries. This implementation is designed for:
- No heap allocation: Fixed-size bucket storage using const generics (
Histogram<N>) Send + Sync: All fields areCopyprimitives; safe to share across threads with external synchronization- High performance: Lookup table provides ×ばつ speedup over logarithm-based mapping
- Sub-byte counters: 1-bit bucket counters (B1) maximize resolution; auto-widen on overflow
- Configurable table size: Trade static memory for lookup acceleration at higher scales
- Quantile estimation: CDF-walk with linear interpolation over the bucket distribution
- Compact configuration: 2-byte
Settingsstruct pairsScale+Width; histograms track both initial and current settings no_stdcompatible: Only thestd::error::Errorimpls require thestdfeature- Zero
unsafecode: Entirely safe Rust; nounsafeblocks anywhere in the crate - Zero runtime dependencies: Only a build dependency (
expohisto-mapping-gen) for compile-time table generation - Comprehensive testing: Unit tests and fuzz targets
Minimum Supported Rust Version (MSRV): 1.83
HistogramNNis positive-only —HistogramNN<N>(aliased asHistogram<N>) implements a single positive bucket set; negative values are rejected. This is suitable for the common case of non-negative measurements (latencies, sizes, counts). For values of any sign, useHistogramPN<K, L>which maintains independent positive and negative bucket ranges with synchronized scales.
use otel_expohisto::Histogram; // Create a histogram with 16 u64 words (128 bytes) of data pool. // All 16 words are available for bucket data: 1024 one-bit buckets // at the default B1 width. let mut hist: Histogram<16> = Histogram::new(); // Record observations hist.update(1.5).unwrap(); hist.update(2.7).unwrap(); hist.update(100.0).unwrap(); // Access statistics through a view let v = hist.view(); let stats = v.stats(); println!("count: {}, sum: {}", stats.count, stats.sum); println!("scale: {}", v.scale());
Benchmark results — map_to_index over 100 random f64 values (criterion, median):
| Method | Scale range | Per-value | Notes |
|---|---|---|---|
| Exponent | ≤ 0 | ~3.4 ns | Bit extraction only |
| Lookup table | 1–14 | ~5.9 ns | Integer-only, compile-time generated |
| Logarithm | 1–20 | ~10.5 ns | ln()-based, works at any scale |
The lookup table accelerates all scales from 1 up to the compiled maximum. Scales beyond the table maximum are rejected by Scale::new().
Scale features scale-1 through scale-16 control the lookup table size.
Each table supports all scales from 1 up to its maximum; higher scales
are rejected by Scale::new(). Selected examples:
| Feature | Table Size | Scales Accelerated | Use Case |
|---|---|---|---|
scale-4 |
152 B | 1–4 | Minimal memory |
scale-6 |
536 B | 1–6 | Embedded systems |
scale-8 |
2 KB | 1–8 | Default |
scale-10 |
8 KB | 1–10 | Recommended |
scale-12 |
32 KB | 1–12 | High resolution |
scale-14 |
128 KB | 1–14 | Maximum practical coverage |
| Feature | Default | Effect |
|---|---|---|
std |
✓ | Enables std::error::Error impls for Overflow and ScaleError. Disable for #![no_std] builds. |
logarithm |
Pure ln()-based mapper for testing and benchmarking. Requires std. |
|
boundary |
Enables lower_boundary() at positive scales. Requires std. |
|
quantile |
Quantile estimation (QuantileIter). Requires boundary. |
|
bench-internals |
Exposes internal methods (e.g., downscale()) for benchmarking |
|
bench-all |
Enables logarithm + quantile + scale-8 + bench-internals for comprehensive testing |
- Design & Architecture — Exponential scale theory, index mapping algorithms, lookup table design, crate structure
- Implementation Details — Literal mode, sub-byte counters (SWAR), parameter selection, API overview
- Reference — Error handling, thread safety,
no_stdsupport, testing, OTel spec compatibility - Historical Notes — Origins of the lookup table algorithm
See CONTRIBUTING.md for build, test, fuzz, and PR guidelines.
Quick validation:
cargo clippy --all-targets --features bench-all -- -D warnings
cargo test --features bench-all
cargo check --manifest-path fuzz/Cargo.toml --all-targetsApache-2.0