Skip to content

Commit 9003154

Browse files
aclementsgopherbot
authored andcommitted
testing: separate b.Loop counter from b.N
Currently, b.Loop uses b.N as the iteration count target. However, since it updates the target as it goes, the behavior is quite different from a b.N-style benchmark. To avoid user confusion, this CL gives b.Loop a separate, unexported iteration count target. It ensures b.N is 0 within the b.Loop loop to help catch misuses, and commits the final iteration count to b.N only once the loop is done (as the documentation states "After Loop returns false, b.N contains the total number of iterations that ran, so the benchmark may use b.N to compute other average metrics.") Since there are now two variables used by b.Loop, we put them in an unnamed struct. Also, we rename b.loopN to b.loop.i because this variable tracks the current iteration index (conventionally "i"), not the target (conventionally "n"). Unfortunately, a simple renaming causes B.Loop to be too large for the inliner. Thus, we make one simplification to B.Loop to keep it under the threshold. We're about to lean into that simplification anyway in a follow-up CL, so this is just temporary. Prep for #72933 and #72971. Change-Id: Ide1c4f1b9ca37f300f3beb0e60ba6202331b183e Reviewed-on: https://go-review.googlesource.com/c/go/+/659655 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Junyang Shao <[email protected]> Auto-Submit: Austin Clements <[email protected]>
1 parent afe11db commit 9003154

File tree

2 files changed

+47
-19
lines changed

2 files changed

+47
-19
lines changed

src/testing/benchmark.go

+35-19
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,17 @@ type B struct {
114114
netBytes uint64
115115
// Extra metrics collected by ReportMetric.
116116
extra map[string]float64
117-
// For Loop() to be executed in benchFunc.
118-
// Loop() has its own control logic that skips the loop scaling.
119-
// See issue #61515.
120-
loopN int
117+
118+
// loop tracks the state of B.Loop
119+
loop struct {
120+
// n is the target number of iterations. It gets bumped up as we go.
121+
// When the benchmark loop is done, we commit this to b.N so users can
122+
// do reporting based on it, but we avoid exposing it until then.
123+
n int
124+
// i is the current Loop iteration. It's strictly monotonically
125+
// increasing toward n.
126+
i int
127+
}
121128
}
122129

123130
// StartTimer starts timing a test. This function is called automatically
@@ -192,7 +199,8 @@ func (b *B) runN(n int) {
192199
runtime.GC()
193200
b.resetRaces()
194201
b.N = n
195-
b.loopN = 0
202+
b.loop.n = 0
203+
b.loop.i = 0
196204
b.ctx = ctx
197205
b.cancelCtx = cancelCtx
198206

@@ -312,8 +320,8 @@ func (b *B) launch() {
312320
}()
313321

314322
// b.Loop does its own ramp-up logic so we just need to run it once.
315-
// If b.loopN is non zero, it means b.Loop has already run.
316-
if b.loopN == 0 {
323+
// If b.loop.n is non zero, it means b.Loop has already run.
324+
if b.loop.n == 0 {
317325
// Run the benchmark for at least the specified amount of time.
318326
if b.benchTime.n > 0 {
319327
// We already ran a single iteration in run1.
@@ -372,34 +380,40 @@ func (b *B) stopOrScaleBLoop() bool {
372380
if t >= b.benchTime.d {
373381
// Stop the timer so we don't count cleanup time
374382
b.StopTimer()
383+
// Commit iteration count
384+
b.N = b.loop.n
375385
return false
376386
}
377387
// Loop scaling
378388
goalns := b.benchTime.d.Nanoseconds()
379-
prevIters := int64(b.N)
380-
b.N = predictN(goalns, prevIters, t.Nanoseconds(), prevIters)
381-
b.loopN++
389+
prevIters := int64(b.loop.n)
390+
b.loop.n = predictN(goalns, prevIters, t.Nanoseconds(), prevIters)
391+
b.loop.i++
382392
return true
383393
}
384394

385395
func (b *B) loopSlowPath() bool {
386-
if b.loopN == 0 {
396+
if b.loop.n == 0 {
387397
// If it's the first call to b.Loop() in the benchmark function.
388398
// Allows more precise measurement of benchmark loop cost counts.
389-
// Also initialize b.N to 1 to kick start loop scaling.
390-
b.N = 1
391-
b.loopN = 1
399+
// Also initialize target to 1 to kick start loop scaling.
400+
b.loop.n = 1
401+
// Within a b.Loop loop, we don't use b.N (to avoid confusion).
402+
b.N = 0
403+
b.loop.i++
392404
b.ResetTimer()
393405
return true
394406
}
395407
// Handles fixed iterations case
396408
if b.benchTime.n > 0 {
397-
if b.N < b.benchTime.n {
398-
b.N = b.benchTime.n
399-
b.loopN++
409+
if b.loop.n < b.benchTime.n {
410+
b.loop.n = b.benchTime.n
411+
b.loop.i++
400412
return true
401413
}
402414
b.StopTimer()
415+
// Commit iteration count
416+
b.N = b.loop.n
403417
return false
404418
}
405419
// Handles fixed time case
@@ -440,8 +454,10 @@ func (b *B) loopSlowPath() bool {
440454
// whereas b.N-based benchmarks must run the benchmark function (and any
441455
// associated setup and cleanup) several times.
442456
func (b *B) Loop() bool {
443-
if b.loopN != 0 && b.loopN < b.N {
444-
b.loopN++
457+
// On the first call, both i and n are 0, so we'll fall through to the slow
458+
// path in that case, too.
459+
if b.loop.i < b.loop.n {
460+
b.loop.i++
445461
return true
446462
}
447463
return b.loopSlowPath()

src/testing/loop_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@ func TestBenchmarkBLoop(t *T) {
1111
var runningEnd bool
1212
runs := 0
1313
iters := 0
14+
firstBN := 0
15+
restBN := 0
1416
finalBN := 0
1517
bRet := Benchmark(func(b *B) {
1618
initialStart = b.start
1719
runs++
1820
for b.Loop() {
1921
if iters == 0 {
2022
firstStart = b.start
23+
firstBN = b.N
24+
} else {
25+
restBN = max(restBN, b.N)
2126
}
2227
if iters == 1 {
2328
scaledStart = b.start
@@ -39,6 +44,13 @@ func TestBenchmarkBLoop(t *T) {
3944
if finalBN != iters || bRet.N != iters {
4045
t.Errorf("benchmark iterations mismatch: %d loop iterations, final b.N=%d, bRet.N=%d", iters, finalBN, bRet.N)
4146
}
47+
// Verify that b.N was 0 inside the loop
48+
if firstBN != 0 {
49+
t.Errorf("want b.N == 0 on first iteration, got %d", firstBN)
50+
}
51+
if restBN != 0 {
52+
t.Errorf("want b.N == 0 on subsequent iterations, got %d", restBN)
53+
}
4254
// Make sure the benchmark ran for an appropriate amount of time.
4355
if bRet.T < benchTime.d {
4456
t.Fatalf("benchmark ran for %s, want >= %s", bRet.T, benchTime.d)

0 commit comments

Comments
 (0)