What is testing.B.Loop (Go 1.24) and why is it preferred over the classic b.N loop?
Question 375MediumGo 1.22 to 1.25
b.Loop() returns true while the benchmark should keep iterating. It fixes three classic b.N mistakes:
- Setup cost: the timer is reset on the first call, so expensive setup before the loop is excluded without
b.ResetTimer(). Cleanup after the loop is also excluded. - Dead-code elimination: the compiler keeps the arguments and results of calls inside a
b.Loopbody alive, so an inlined pure function can't be optimized away. - Setup runs once: with b.N, the whole function runs multiple times (b.N = 1, 100, 10000, ...), repeating setup each time. With
b.Loop, the function body is executed once and iteration ramp-up happens inside the loop.
func BenchmarkParse(b *testing.B) {
input := loadFixture(b) // excluded from timing automatically
b.ReportAllocs()
for b.Loop() {
parse(input) // result kept alive; not eliminated
}
}
// Old style (still valid, but easy to get wrong):
func BenchmarkParseOld(b *testing.B) {
input := loadFixture(b)
b.ResetTimer()
for range b.N {
parse(input)
}
}
Gotchas: call b.Loop in exactly one loop per benchmark and don't mix it with b.N. Don't reference b.N inside a b.Loop benchmark to size things.
More on Performance, Profiling & Testing
- Q373What do the block and mutex profiles measure, how do you enable them, and how do they differ?
- Q374When would you use go tool trace instead of pprof? What can it show that profiles cannot?
- Q376What does this benchmark report, and why is it wrong?
- Q377How do you run benchmarks rigorously and compare two implementations? Explain -benchmem, ReportAllocs, and benchstat.
- Q378Write an idiomatic table-driven test with subtests. Why is this the preferred Go style?
- Q379What does this test print, and in what order?