Why don't Go benchmarks finish?

One of the most common mistakes when writing Go benchmarks is to vary the argument to the code being benchmarked. The example below might never finish. Even if it does, the execution time increases with the argument so the reported ns/op isn't very useful. It's an average of timing samples that keep increasing.

func sumSquares(n int) int {
	sum := 0
	for i := 1; i <= n; i++ {
		sum += i * i
	}
	return sum
}

func BenchmarkMistake(b *testing.B) {
	for i := 1; i <= b.N; i++ {
		sumSquares(i)
	}
}

Another easy way to make the same mistake is to use b.N as the argument. b.N sort of looks like a constant but it actually varies. The benchmarking package calls the method being benchmarked multiple times and adjusts b.N until it finds reliable timing.

func BenchmarkMistake2(b *testing.B) {
	sumSquares(b.N)
}

The previous mistakes are less common when the benchmark doesn't take an integer argument.

Finally, the benchmark might be implemented correctly but still have slow execution runtime.

func BenchmarkCorrectButSlow(b *testing.B) {
	for i := 1; i <= b.N; i++ {
		sumSquares(100000000000)
	}
}

Oh and if you do actually want to compute the sum of squares, you can always use the closed form solution.

func sumSquares(x int) int {
	return x * (x + 1) * (2*x + 1) / 6
}

Hi, I'm Eddie Scholtz. These are my notes. You can reach me at eascholtz@gmail.com. Atom