Skip to content

Commit 090748d

Browse files
aclementsgopherbot
authored andcommitted
testing: improve B.Loop docs, use B.Loop in examples
This updates the testing documentation to frame B.Loop as the canonical way to write benchmarks. We retain documentation on b.N benchmarks because people will definitely continue to see them (and write them), but it's demoted to clearly second class. This also attempts to clarify and refine the B.Loop documentation itself. Updates #61515 Fixes #70787 Change-Id: If5123435bfe3a5883a753119ecdf7bbc41afd499 Reviewed-on: https://go-review.googlesource.com/c/go/+/635895 Reviewed-by: Junyang Shao <[email protected]> Reviewed-by: Caleb Spare <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Austin Clements <[email protected]>
1 parent e39e965 commit 090748d

File tree

3 files changed

+71
-33
lines changed

3 files changed

+71
-33
lines changed

src/testing/benchmark.go

+32-22
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ type InternalBenchmark struct {
7878
}
7979

8080
// B is a type passed to [Benchmark] functions to manage benchmark
81-
// timing and to specify the number of iterations to run.
81+
// timing and control the number of iterations.
8282
//
8383
// A benchmark ends when its Benchmark function returns or calls any of the methods
8484
// FailNow, Fatal, Fatalf, SkipNow, Skip, or Skipf. Those methods must be called
@@ -133,8 +133,7 @@ func (b *B) StartTimer() {
133133
}
134134

135135
// StopTimer stops timing a test. This can be used to pause the timer
136-
// while performing complex initialization that you don't
137-
// want to measure.
136+
// while performing steps that you don't want to measure.
138137
func (b *B) StopTimer() {
139138
if b.timerOn {
140139
b.duration += highPrecisionTimeSince(b.start)
@@ -387,7 +386,7 @@ func (b *B) loopSlowPath() bool {
387386
b.ResetTimer()
388387
return true
389388
}
390-
// Handles fixed time case
389+
// Handles fixed iterations case
391390
if b.benchTime.n > 0 {
392391
if b.N < b.benchTime.n {
393392
b.N = b.benchTime.n
@@ -396,31 +395,42 @@ func (b *B) loopSlowPath() bool {
396395
}
397396
return false
398397
}
399-
// Handles fixed iteration count case
398+
// Handles fixed time case
400399
return b.stopOrScaleBLoop()
401400
}
402401

403-
// Loop returns true until b.N calls has been made to it.
404-
//
405-
// A benchmark should either use Loop or contain an explicit loop from 0 to b.N, but not both.
406-
// After the benchmark finishes, b.N will contain the total number of calls to op, so the benchmark
407-
// may use b.N to compute other average metrics.
402+
// Loop returns true as long as the benchmark should continue running.
408403
//
409-
// The parameters and results of function calls inside the body of "for b.Loop() {...}" are guaranteed
410-
// not to be optimized away.
411-
// Also, the local loop scaling for b.Loop ensures the benchmark function containing the loop will only
412-
// be executed once, i.e. for such construct:
404+
// A typical benchmark is structured like:
413405
//
414-
// testing.Benchmark(func(b *testing.B) {
415-
// ...(setup)
416-
// for b.Loop() {
417-
// ...(benchmark logic)
418-
// }
419-
// ...(clean-up)
406+
// func Benchmark(b *testing.B) {
407+
// ... setup ...
408+
// for b.Loop() {
409+
// ... code to measure ...
410+
// }
411+
// ... cleanup ...
420412
// }
421413
//
422-
// The ...(setup) and ...(clean-up) logic will only be executed once.
423-
// Also benchtime=Nx (N>1) will result in exactly N executions instead of N+1 for b.N style loops.
414+
// Loop resets the benchmark timer the first time it is called in a benchmark,
415+
// so any setup performed prior to starting the benchmark loop does not count
416+
// toward the benchmark measurement.
417+
//
418+
// The compiler never optimizes away calls to functions within the body of a
419+
// "for b.Loop() { ... }" loop. This prevents surprises that can otherwise occur
420+
// if the compiler determines that the result of a benchmarked function is
421+
// unused. The loop must be written in exactly this form, and this only applies
422+
// to calls syntactically between the curly braces of the loop. Optimizations
423+
// are performed as usual in any functions called by the loop.
424+
//
425+
// After Loop returns false, b.N contains the total number of iterations that
426+
// ran, so the benchmark may use b.N to compute other average metrics.
427+
//
428+
// Prior to the introduction of Loop, benchmarks were expected to contain an
429+
// explicit loop from 0 to b.N. Benchmarks should either use Loop or contain a
430+
// loop to b.N, but not both. Loop offers more automatic management of the
431+
// benchmark timer, and runs each benchmark function only once per measurement,
432+
// whereas b.N-based benchmarks must run the benchmark function (and any
433+
// associated setup and cleanup) several times.
424434
func (b *B) Loop() bool {
425435
if b.loopN != 0 && b.loopN < b.N {
426436
b.loopN++

src/testing/benchmark_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func ExampleB_Loop() {
155155
}
156156
n := 0
157157
testing.Benchmark(func(b *testing.B) {
158-
// Unlike "for i := range N {...}" style loops, this
158+
// Unlike "for i := range b.N {...}" style loops, this
159159
// setup logic will only be executed once, so simpleFunc
160160
// will always get argument 1.
161161
n++
@@ -219,7 +219,7 @@ func ExampleB_ReportMetric() {
219219
// specific algorithm (in this case, sorting).
220220
testing.Benchmark(func(b *testing.B) {
221221
var compares int64
222-
for i := 0; i < b.N; i++ {
222+
for b.Loop() {
223223
s := []int{5, 4, 3, 2, 1}
224224
slices.SortFunc(s, func(a, b int) int {
225225
compares++

src/testing/testing.go

+37-9
Original file line numberDiff line numberDiff line change
@@ -72,27 +72,24 @@
7272
// A sample benchmark function looks like this:
7373
//
7474
// func BenchmarkRandInt(b *testing.B) {
75-
// for range b.N {
75+
// for b.Loop() {
7676
// rand.Int()
7777
// }
7878
// }
7979
//
80-
// The benchmark function must run the target code b.N times.
81-
// It is called multiple times with b.N adjusted until the
82-
// benchmark function lasts long enough to be timed reliably.
8380
// The output
8481
//
8582
// BenchmarkRandInt-8 68453040 17.8 ns/op
8683
//
87-
// means that the loop ran 68453040 times at a speed of 17.8 ns per loop.
84+
// means that the body of the loop ran 68453040 times at a speed of 17.8 ns per loop.
8885
//
89-
// If a benchmark needs some expensive setup before running, the timer
90-
// may be reset:
86+
// Only the body of the loop is timed, so benchmarks may do expensive
87+
// setup before calling b.Loop, which will not be counted toward the
88+
// benchmark measurement:
9189
//
9290
// func BenchmarkBigLen(b *testing.B) {
9391
// big := NewBig()
94-
// b.ResetTimer()
95-
// for range b.N {
92+
// for b.Loop() {
9693
// big.Len()
9794
// }
9895
// }
@@ -120,6 +117,37 @@
120117
// In particular, https://golang.org/x/perf/cmd/benchstat performs
121118
// statistically robust A/B comparisons.
122119
//
120+
// # b.N-style benchmarks
121+
//
122+
// Prior to the introduction of [B.Loop], benchmarks were written in a
123+
// different style using [B.N]. For example:
124+
//
125+
// func BenchmarkRandInt(b *testing.B) {
126+
// for range b.N {
127+
// rand.Int()
128+
// }
129+
// }
130+
//
131+
// In this style of benchmark, the benchmark function must run
132+
// the target code b.N times. The benchmark function is called
133+
// multiple times with b.N adjusted until the benchmark function
134+
// lasts long enough to be timed reliably. This also means any setup
135+
// done before the loop may be run several times.
136+
//
137+
// If a benchmark needs some expensive setup before running, the timer
138+
// should be explicitly reset:
139+
//
140+
// func BenchmarkBigLen(b *testing.B) {
141+
// big := NewBig()
142+
// b.ResetTimer()
143+
// for range b.N {
144+
// big.Len()
145+
// }
146+
// }
147+
//
148+
// New benchmarks should prefer using [B.Loop], which is more robust
149+
// and more efficient.
150+
//
123151
// # Examples
124152
//
125153
// The package also runs and verifies example code. Example functions may

0 commit comments

Comments
 (0)