Package list golang-github-beorn7-perks / b848f97
Avoid iterating on maps Speed up `InsertTargeted*` functions by at least 2x by avoiding iterating on maps. Before and after benchmark comparison: benchmark old ns/op new ns/op delta BenchmarkInsertTargeted-4 177 73.2 -58.64% BenchmarkInsertTargetedSmallEpsilon-4 322 94.6 -70.62% BenchmarkInsertBiased-4 75.8 74.7 -1.45% BenchmarkInsertBiasedSmallEpsilon-4 563 571 +1.42% BenchmarkQuery-4 7426 1118 -84.94% BenchmarkQuerySmallEpsilon-4 80390 12535 -84.41% benchmark old allocs new allocs delta BenchmarkInsertTargeted-4 0 0 +0.00% benchmark old bytes new bytes delta BenchmarkInsertTargeted-4 0 0 +0.00% I considered changing the function signature and requiring users to pass a slice to avoid the conversion, but I think that would inconvenience users of this library unnecessarily. Found by profiling the CPU time spent calculating quantiles in the Prometheus Go client library. Matt Bostock 3 years ago
1 changed file(s) with 29 addition(s) and 5 deletion(s). Raw diff Collapse all Expand all
7676 // is guaranteed to be within (Quantile±Epsilon).
7777 //
7878 // See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
79 func NewTargeted(targets map[float64]float64) *Stream {
79 func NewTargeted(targetMap map[float64]float64) *Stream {
80 // Convert map to slice to avoid slow iterations on a map.
81 // ƒ is called on the hot path, so converting the map to a slice
82 // beforehand results in significant CPU savings.
83 targets := targetMapToSlice(targetMap)
84
8085 ƒ := func(s *stream, r float64) float64 {
8186 var m = math.MaxFloat64
8287 var f float64
83 for quantile, epsilon := range targets {
84 if quantile*s.n <= r {
85 f = (2 * epsilon * r) / quantile
88 for _, t := range targets {
89 if t.quantile*s.n <= r {
90 f = (2 * t.epsilon * r) / t.quantile
8691 } else {
87 f = (2 * epsilon * (s.n - r)) / (1 - quantile)
92 f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
8893 }
8994 if f < m {
9095 m = f
9398 return m
9499 }
95100 return newStream(ƒ)
101 }
102
103 type target struct {
104 quantile float64
105 epsilon float64
106 }
107
108 func targetMapToSlice(targetMap map[float64]float64) []target {
109 targets := make([]target, 0, len(targetMap))
110
111 for quantile, epsilon := range targetMap {
112 t := target{
113 quantile: quantile,
114 epsilon: epsilon,
115 }
116 targets = append(targets, t)
117 }
118
119 return targets
96120 }
97121
98122 // Stream computes quantiles for a stream of float64s. It is not thread-safe by