Improve bucketing of native histograms with positive schema
Following #1915, I've also started thinking about implementing native histograms in the Rust client. Looking at the current Go implementation:
https://github.com/prometheus/client_golang/blob/c316de06dea0caf0631f9eca944bc74cca1eb19c/prometheus/histogram.go#L661-L698
I've spotted several things to improve:
-
Infbucket can be computed directly, avoiding an extraif 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.5can be avoided by usingmath.Frexp(math.Nextafter(math.Abs(v), math.Inf(-1)))(zero threshold must be checked first) - a single
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 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.
I forgot to add that math.Pow(frac, float64(int(1)<<schema)) can be replaced by simpler function that gives the same result with much better performance. For example
func pow(f float64, schema int) float64 {
result := f
for i := 0; i < schema; i++ {
result *= result
}
return result
}
In my experiments, I tested various implementations of pow. However, Rust compiler is very different from Go, so I can't expect the same optimizations. But here is the Go version that should give:
func pow(f float64, schema int) float64 {
result := f
switch schema {
case 8:
result *= result
fallthrough
case 7:
result *= result
fallthrough
case 6:
result *= result
fallthrough
case 5:
result *= result
fallthrough
case 4:
result *= result
fallthrough
case 3:
result *= result
fallthrough
case 2:
result *= result
fallthrough
case 1:
result *= result
}
return result
}