Skip to content

Commit a6c2549

Browse files
committed
fix(testing): save measurements only in the benchmark framework
1 parent fbe2077 commit a6c2549

3 files changed

Lines changed: 110 additions & 6 deletions

File tree

testing/fork.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ find . -type f -name "*.go" -not -name "*_test.go" -exec sed -i 's|"testing"|tes
122122
restore_files "${CODSPEED_FILES[@]}"
123123

124124
apply_patch "patches/benchmark_stopbenchmark_fail.patch" 10 ".."
125+
apply_patch "patches/benchmark_stoptimer_mitigation.patch" 10 ".."
125126

126127

127128
# Run pre-commit and format the code
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
diff --git a/testing/testing/benchmark.go b/testing/testing/benchmark.go
2+
index 8fdea90..4947e11 100644
3+
--- a/testing/testing/benchmark.go
4+
+++ b/testing/testing/benchmark.go
5+
@@ -93,6 +93,10 @@ type codspeed struct {
6+
startTimestamp uint64
7+
startTimestamps []uint64
8+
stopTimestamps []uint64
9+
+
10+
+ // Indicates whether a measurement has been saved already. This aims to prevent saving measurements
11+
+ // twice, because `b.Loop()` saves them internally as well but is also called from runN
12+
+ savedMeasurement bool
13+
}
14+
15+
// B is a type passed to [Benchmark] functions to manage benchmark
16+
@@ -160,6 +164,7 @@ func (b *B) StartTimerWithoutMarker() {
17+
// b.startBytes = memStats.TotalAlloc
18+
b.start = highPrecisionTimeNow()
19+
b.timerOn = true
20+
+ b.savedMeasurement = false
21+
// b.loop.i &^= loopPoisonTimer
22+
}
23+
}
24+
@@ -186,14 +191,32 @@ func (b *B) StopTimerWithoutMarker() {
25+
b.timerOn = false
26+
// If we hit B.Loop with the timer stopped, fail.
27+
// b.loop.i |= loopPoisonTimer
28+
+ }
29+
+}
30+
31+
- // For b.N loops: This will be called in runN which sets b.N to the number of iterations.
32+
- // For b.Loop() loops: loopSlowPath sets b.N to 0 to prevent b.N loops within b.Loop. However, since
33+
- // we're starting/stopping the timer for each iteration in the b.Loop() loop, we can use 1 as
34+
- // the number of iterations for this round.
35+
- b.codspeedItersPerRound = append(b.codspeedItersPerRound, max(int64(b.N), 1))
36+
- b.codspeedTimePerRoundNs = append(b.codspeedTimePerRoundNs, timeSinceStart)
37+
+func (b *B) SaveMeasurement() {
38+
+ if b.savedMeasurement {
39+
+ return
40+
}
41+
+ b.savedMeasurement = true
42+
+
43+
+ // For b.N loops: This will be called in runN which sets b.N to the number of iterations.
44+
+ // For b.Loop() loops: loopSlowPath sets b.N to 0 to prevent b.N loops within b.Loop. However, since
45+
+ // we're starting/stopping the timer for each iteration in the b.Loop() loop, we can use 1 as
46+
+ // the number of iterations for this round.
47+
+ timeSinceStart := highPrecisionTimeSince(b.start)
48+
+
49+
+ // If this gets called from b.Loop(), we have to take the duration compared to the previous StartTimer,
50+
+ // if it's called from runN, we can use b.duration
51+
+ duration := time.Duration(0)
52+
+ if b.N == 0 {
53+
+ duration = timeSinceStart
54+
+ } else {
55+
+ duration = b.duration
56+
+ }
57+
+
58+
+ b.codspeedItersPerRound = append(b.codspeedItersPerRound, max(int64(b.N), 1))
59+
+ b.codspeedTimePerRoundNs = append(b.codspeedTimePerRoundNs, duration)
60+
}
61+
62+
func (b *B) StopTimer() {
63+
@@ -296,6 +319,7 @@ func (b *B) __codspeed_root_frame__runN(n int) {
64+
b.ResetTimer()
65+
b.StartTimer()
66+
b.benchFunc(b)
67+
+ b.SaveMeasurement()
68+
b.StopTimer()
69+
b.previousN = n
70+
b.previousDuration = b.duration
71+
@@ -614,6 +638,7 @@ func (b *B) loopSlowPath() bool {
72+
// whereas b.N-based benchmarks must run the benchmark function (and any
73+
// associated setup and cleanup) several times.
74+
func (b *B) Loop() bool {
75+
+ b.SaveMeasurement()
76+
b.StopTimerWithoutMarker()
77+
// This is written such that the fast path is as fast as possible and can be
78+
// inlined.

testing/testing/benchmark.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ type codspeed struct {
9393
startTimestamp uint64
9494
startTimestamps []uint64
9595
stopTimestamps []uint64
96+
97+
// Indicates whether a measurement has been saved already. This aims to prevent saving measurements
98+
// twice, because `b.Loop()` saves them internally as well but is also called from runN
99+
savedMeasurement bool
96100
}
97101

98102
// B is a type passed to [Benchmark] functions to manage benchmark
@@ -160,6 +164,7 @@ func (b *B) StartTimerWithoutMarker() {
160164
// b.startBytes = memStats.TotalAlloc
161165
b.start = highPrecisionTimeNow()
162166
b.timerOn = true
167+
b.savedMeasurement = false
163168
// b.loop.i &^= loopPoisonTimer
164169
}
165170
}
@@ -186,14 +191,32 @@ func (b *B) StopTimerWithoutMarker() {
186191
b.timerOn = false
187192
// If we hit B.Loop with the timer stopped, fail.
188193
// b.loop.i |= loopPoisonTimer
194+
}
195+
}
189196

190-
// For b.N loops: This will be called in runN which sets b.N to the number of iterations.
191-
// For b.Loop() loops: loopSlowPath sets b.N to 0 to prevent b.N loops within b.Loop. However, since
192-
// we're starting/stopping the timer for each iteration in the b.Loop() loop, we can use 1 as
193-
// the number of iterations for this round.
194-
b.codspeedItersPerRound = append(b.codspeedItersPerRound, max(int64(b.N), 1))
195-
b.codspeedTimePerRoundNs = append(b.codspeedTimePerRoundNs, timeSinceStart)
197+
func (b *B) SaveMeasurement() {
198+
if b.savedMeasurement {
199+
return
196200
}
201+
b.savedMeasurement = true
202+
203+
// For b.N loops: This will be called in runN which sets b.N to the number of iterations.
204+
// For b.Loop() loops: loopSlowPath sets b.N to 0 to prevent b.N loops within b.Loop. However, since
205+
// we're starting/stopping the timer for each iteration in the b.Loop() loop, we can use 1 as
206+
// the number of iterations for this round.
207+
timeSinceStart := highPrecisionTimeSince(b.start)
208+
209+
// If this gets called from b.Loop(), we have to take the duration compared to the previous StartTimer,
210+
// if it's called from runN, we can use b.duration
211+
duration := time.Duration(0)
212+
if b.N == 0 {
213+
duration = timeSinceStart
214+
} else {
215+
duration = b.duration
216+
}
217+
218+
b.codspeedItersPerRound = append(b.codspeedItersPerRound, max(int64(b.N), 1))
219+
b.codspeedTimePerRoundNs = append(b.codspeedTimePerRoundNs, duration)
197220
}
198221

199222
func (b *B) StopTimer() {
@@ -296,6 +319,7 @@ func (b *B) __codspeed_root_frame__runN(n int) {
296319
b.ResetTimer()
297320
b.StartTimer()
298321
b.benchFunc(b)
322+
b.SaveMeasurement()
299323
b.StopTimer()
300324
b.previousN = n
301325
b.previousDuration = b.duration
@@ -614,6 +638,7 @@ func (b *B) loopSlowPath() bool {
614638
// whereas b.N-based benchmarks must run the benchmark function (and any
615639
// associated setup and cleanup) several times.
616640
func (b *B) Loop() bool {
641+
b.SaveMeasurement()
617642
b.StopTimerWithoutMarker()
618643
// This is written such that the fast path is as fast as possible and can be
619644
// inlined.

0 commit comments

Comments
 (0)