Skip to content

Commit c7ac7d8

Browse files
committed
roachprod-microbench: confidence interval calculation
This change adds the ability to calculate the bootstrap confidence interval given two sets of microbenchmark results. When applying the bootstrap confidence interval method to microbenchmarks, it helps estimate the uncertainty in performance measurements. By resampling benchmark results with replacement and computing the statistic (e.g., mean) over multiple iterations, we obtain a distribution of possible outcomes. The confidence interval derived from this distribution provides a robust estimate of performance variability, accounting for noise and system fluctuations without assuming normality. Epic: None Release note: None
1 parent bdbdbf1 commit c7ac7d8

File tree

4 files changed

+90
-4
lines changed

4 files changed

+90
-4
lines changed

pkg/cmd/roachprod-microbench/model/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go_library(
44
name = "model",
55
srcs = [
66
"builder.go",
7+
"math.go",
78
"metric.go",
89
"options.go",
910
],

pkg/cmd/roachprod-microbench/model/builder.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func (m *Metric) ComputeComparison(benchmarkName, oldID, newID string) *Comparis
9595
return nil
9696
}
9797
}
98-
// Compute the comparison and delta.
98+
// Compute the comparison, confidence interval and delta.
9999
comparison := Comparison{}
100100
oldSample, newSample := benchmarkEntry.Samples[oldID], benchmarkEntry.Samples[newID]
101101
comparison.Distribution = m.Assumption.Compare(oldSample, newSample)
@@ -106,6 +106,7 @@ func (m *Metric) ComputeComparison(benchmarkName, oldID, newID string) *Comparis
106106
} else {
107107
comparison.Delta = ((newSummary.Center / oldSummary.Center) - 1.0) * 100
108108
}
109+
comparison.ConfidenceInterval = calculateConfidenceInterval(newSample.Values, oldSample.Values)
109110
return &comparison
110111
}
111112

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2025 The Cockroach Authors.
2+
//
3+
// Use of this software is governed by the CockroachDB Software License
4+
// included in the /LICENSE file.
5+
//
6+
7+
package model
8+
9+
import (
10+
"math"
11+
"math/rand"
12+
"sort"
13+
)
14+
15+
const resampleCount = 1000
16+
const confidence = 0.95
17+
18+
// calculateConfidenceInterval calculates the confidence interval for the ratio
19+
// of two sets of values. The confidence interval is calculated using a
20+
// bootstrap method.
21+
func calculateConfidenceInterval(newValues, oldValues []float64) ConfidenceInterval {
22+
rng := rand.New(rand.NewSource(hash(newValues) + hash(oldValues)))
23+
ratios := make([]float64, 0, resampleCount)
24+
resNew := make([]float64, len(newValues))
25+
resOld := make([]float64, len(oldValues))
26+
for range resampleCount {
27+
resample(rng, newValues, resNew)
28+
sort.Float64s(resNew)
29+
resample(rng, oldValues, resOld)
30+
sort.Float64s(resOld)
31+
32+
medOld := median(resOld)
33+
// Skip if the old median is 0 to avoid division by zero.
34+
if medOld != 0 {
35+
ratios = append(ratios, median(resNew)/medOld)
36+
}
37+
}
38+
if len(ratios) == 0 {
39+
return ConfidenceInterval{}
40+
}
41+
sort.Float64s(ratios)
42+
alpha := (1.0 - confidence) / 2.0
43+
lowerIndex := int(math.Floor(float64(len(ratios)) * alpha))
44+
upperIndex := int(math.Floor(float64(len(ratios)) * (1.0 - alpha)))
45+
return ConfidenceInterval{
46+
Low: ratios[lowerIndex],
47+
High: ratios[upperIndex],
48+
Center: median(ratios),
49+
}
50+
}
51+
52+
// resample samples a slice of values with replacement.
53+
func resample(r *rand.Rand, src, dest []float64) {
54+
length := len(src)
55+
for i := range dest {
56+
dest[i] = src[r.Intn(length)]
57+
}
58+
}
59+
60+
// hash returns an arbitrary hash of the given values.
61+
func hash(data []float64) int64 {
62+
var hashValue int64
63+
for _, d := range data {
64+
hashValue += (int64)(math.Float64bits(d))
65+
}
66+
return hashValue
67+
}
68+
69+
// median returns the median of a sorted slice of values.
70+
func median(values []float64) float64 {
71+
length := len(values)
72+
if length%2 == 0 {
73+
return (values[length/2] + values[length/2-1]) / 2
74+
}
75+
return values[length/2]
76+
}

pkg/cmd/roachprod-microbench/model/metric.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,17 @@ type BenchmarkEntry struct {
3939

4040
// Comparison contains the results of comparing two microbenchmarks.
4141
type Comparison struct {
42-
Distribution benchmath.Comparison
43-
Delta float64
44-
FormattedDelta string
42+
Distribution benchmath.Comparison
43+
ConfidenceInterval ConfidenceInterval
44+
Delta float64
45+
FormattedDelta string
46+
}
47+
48+
// ConfidenceInterval holds the low and high bounds of a confidence interval.
49+
type ConfidenceInterval struct {
50+
Low float64
51+
High float64
52+
Center float64
4553
}
4654

4755
// ComparisonResult holds the comparison results for a specific metric.

0 commit comments

Comments
 (0)