Following #1915, I've also started thinking about implementing native histograms in the Rust client. Looking at the current Go implementation:
|
var ( |
|
key int |
|
schema = atomic.LoadInt32(&hc.nativeHistogramSchema) |
|
zeroThreshold = math.Float64frombits(atomic.LoadUint64(&hc.nativeHistogramZeroThresholdBits)) |
|
bucketCreated, isInf bool |
|
) |
|
if math.IsInf(v, 0) { |
|
// Pretend v is MaxFloat64 but later increment key by one. |
|
if math.IsInf(v, +1) { |
|
v = math.MaxFloat64 |
|
} else { |
|
v = -math.MaxFloat64 |
|
} |
|
isInf = true |
|
} |
|
frac, exp := math.Frexp(math.Abs(v)) |
|
if schema > 0 { |
|
bounds := nativeHistogramBounds[schema] |
|
key = sort.SearchFloat64s(bounds, frac) + (exp-1)*len(bounds) |
|
} else { |
|
key = exp |
|
if frac == 0.5 { |
|
key-- |
|
} |
|
offset := (1 << -schema) - 1 |
|
key = (key + offset) >> -schema |
|
} |
|
if isInf { |
|
key++ |
|
} |
|
switch { |
|
case v > zeroThreshold: |
|
bucketCreated = addToBucket(&hc.nativeHistogramBucketsPositive, key, 1) |
|
case v < -zeroThreshold: |
|
bucketCreated = addToBucket(&hc.nativeHistogramBucketsNegative, key, 1) |
|
default: |
|
atomic.AddUint64(&hc.nativeHistogramZeroBucket, 1) |
|
} |
I've spotted several things to improve:
Inf bucket can be computed directly, avoiding an extra if isInf
- bucket for positive schema can be computed with:
_, exp2 := math.Frexp(math.Pow(frac, float64(int(1)<<schema))); exp2 + (exp << schema)
if frac == 0.5 can be avoided by using math.Frexp(math.Nextafter(math.Abs(v), math.Inf(-1))) (zero threshold must be checked first)
- a single
math.Abs(v) > zeroThreshold can replace the two tests (since math.Abs(v) is already computed); then nativeHistogramBucketsPositive, nativeHistogramBucketsNegative could become [2]sync.Map, indexed by math.Signbit(v)
math.Frexp, as well as math.Nextafter can be replaced by a simplified version that skips special cases (NaN, Inf, etc.), since they’re already filtered out
The most important optimization is bucket computation — avoiding binary search, which gets slow as arrays grow (especially schema 8).
I’ve done experiments in Rust: https://github.com/wyfo/native-histogram (benchmark results can be found here)
I will try to submit a PR — hopefully next week.
Following #1915, I've also started thinking about implementing native histograms in the Rust client. Looking at the current Go implementation:
client_golang/prometheus/histogram.go
Lines 661 to 698 in c316de0
I've spotted several things to improve:
Infbucket can be computed directly, avoiding an extraif isInf_, exp2 := math.Frexp(math.Pow(frac, float64(int(1)<<schema))); exp2 + (exp << schema)if frac == 0.5can be avoided by usingmath.Frexp(math.Nextafter(math.Abs(v), math.Inf(-1)))(zero threshold must be checked first)math.Abs(v) > zeroThresholdcan replace the two tests (sincemath.Abs(v)is already computed); thennativeHistogramBucketsPositive, nativeHistogramBucketsNegativecould become[2]sync.Map, indexed bymath.Signbit(v)math.Frexp, as well asmath.Nextaftercan be replaced by a simplified version that skips special cases (NaN, Inf, etc.), since they’re already filtered outThe most important optimization is bucket computation — avoiding binary search, which gets slow as arrays grow (especially schema 8).
I’ve done experiments in Rust: https://github.com/wyfo/native-histogram (benchmark results can be found here)
I will try to submit a PR — hopefully next week.