Go

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:

  1. 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.
  2. Dead-code elimination: the compiler keeps the arguments and results of calls inside a b.Loop body alive, so an inlined pure function can't be optimized away.
  3. 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

All 38 Performance, Profiling & Testing questions