Dave Cheney在他的blog写了一篇关于Go的基准测试编写的基本介绍(链接)。我以此为内容,整理输出内容。

对自己编写package编写基准测试是必不可少的过程,尤其对于执行性能有重要影响的代码,更是需要。Go向来是以工具丰富而著称的,从代码格式调整,到单元测试,到竞争检查,以及我们接下来要介绍的基准测试,都提供方便的工具给开发者。

我们以Fibonacci函数来介绍。

//fib.go
func Fib(n int) int {
	if n < 2 {
	    return n
	}
	return Fib(n-1) + Fib(n-2)
}

编写第一个benchmark testing:

//fib\_test.go
func BenchmarkFib10(b *testing.B) {
	// 执行b.N 次Fib函数
	for n := 0; n < b.N; n++ {
	    Fib(10)
	}
}

以下几点需要注意:

  • 基准测试需要位于文件名为”{packagename}_test.go”的文件中,如这里的”fib_test.go”
  • 需要引入“testing”包
  • 基准测试函数需要以”Benchmark”为函数名的开头
  • 基准函数会执行许多遍。b.N的没次执行都会自增长,直到工具认为统计数据已经满足基准测试输出的要求。

完成以上代码,我们就可以调用工具执行测试了:

$ go test -bench=.
testing: warning: no tests to run
PASS
BenchmarkFib10   2000000           835 ns/op
ok      test/fib    2.522s

注意:

  • 以上结果的第一行和第二行结果是go test执行的结果。
  • 与go test相似,-bench flag可接收一个有效的正则表达式来执行符合条件的测试函数。

同时,也轻易通过以下代码扩展,得到多个测试基准的结果:

func benchmarkFib(i int, b *testing.B) {
	for n := 0; n < b.N; n++ {
	    Fib(i)
	}
}

func BenchmarkFib1(b *testing.B)  { benchmarkFib(1, b) }
func BenchmarkFib2(b *testing.B)  { benchmarkFib(2, b) }
func BenchmarkFib3(b *testing.B)  { benchmarkFib(3, b) }
func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }

运行结果:

BenchmarkFib1   500000000            7.81 ns/op
BenchmarkFib2   100000000           18.4 ns/op
BenchmarkFib3   100000000           24.9 ns/op
BenchmarkFib10   1000000          1269 ns/op
BenchmarkFib20     20000        109198 ns/op
BenchmarkFib40         1    1780678179 ns/op
ok      test/fib    14.852s

其他说明:

  • 每个测试的运行最小运行时间默认是1s,如果测试返回结果时,运行时间还没达到1s,b.N将以1, 2, 5, 10, 20, 50, … 的序列递增,然后重新运行测试代码。

  • 注意上面的结果中,BenchmarkFib40只运行了一次,这是由于它的执行太慢。为了得到更大的样例数据,你可以通过设置最小运行时间来达到。设置-benchtime flag的即可。

    go test  -bench=Fib40 -benchtime=10s
    PASS
    BenchmarkFib40        10    1711313264 ns/op
    ok      test/fib    18.989s
    

特别注意: * 由于b.N是字增的,所以要谨慎用它来做函数参数。不要患下面这样的错误,否则,测试运行将没法终止。

func BenchmarkFibWrong(b *testing.B) {
	for n := 0; n < b.N; n++ {
	    Fib(n)
	}
}

func BenchmarkFibWrong2(b *testing.B) {
	Fib(b.N)
}