简介

Go标准库中test包包含一个基准测试工具,可用于检查Go代码的性能。 接下来将介绍如何使用测试包编写一个简单的基准测试。

一个基准测试示例

我们以斐波那契数列计算来做测试

func Fib(n int) int {
	if n < 2 {
		return n
	}
	
	return Fib(n-1) + Fib(n-2)
}

创建一个名为*_test.go的测试文件,我们将对计算第20个斐波那契数列值进行性能测试。

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

编写基准测试与编写测试非常相似,因为它们共享测试包中的基础结构。一些关键区别是

  • 基准测试功能以Benchmark而不是Test开头
  • 基准功能由测试包运行多次。 b.N的值每次都会增加,直到基准运行者对基准的稳定性感到满意为止。
  • 每个基准测试必须执行b.N次测试代码。 BenchmarkFib20中的for循环将出现在每个基准测试函数中。

运行基准测试

我们可以使用go test -bench=. 调用基准测试

go test -bench=.

# 运行结果如下
goos: linux
goarch: amd64
pkg: test/benchmark
BenchmarkFib-4             30000             44684 ns/op
PASS
ok      test/benchmark  1.796s

您必须将有效的正则表达式传递给-bench,仅传递-bench是语法错误。您可以使用此属性来运行基准测试的子集

如果要跳过测试,可以通过将正则表达式传递给不匹配任何内容的-run标志来实现。我通常使用

go test -run=XXX -bench=.

第四行BenchmarkFib-4是迭代b.N次的最终值的平均运行时间。我这里是执行Fib(20)运行时间在44684 ns

各种输入的基准测试

由于原始的Fib函数是经典的递归实现,因此我们希望它会随着输入的增长而呈现指数行为。 我们可以通过使用Go标准库中非常常见的模式稍微重写基准来探索这一点

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) }

将benchmarkFib设置为private可避免测试驱动程序尝试直接调用它,因为其签名与func(* testing.B)不匹配将失败。 运行这套新的基准测试获得如下结果:

goos: linux
goarch: amd64
pkg: test/benchmark
BenchmarkFib-4           5000000               362 ns/op
BenchmarkFib1-4         1000000000               2.11 ns/op
BenchmarkFib2-4         300000000                5.86 ns/op
BenchmarkFib3-4         200000000                9.86 ns/op
BenchmarkFib10-4         5000000               358 ns/op
BenchmarkFib20-4           30000             44751 ns/op
BenchmarkFib40-4               2         674276614 ns/op
PASS
ok      test/benchmark  15.811s

除了确认我们简单的Fib函数的指数行为外,在此基准测试运行中还需要观察其他一些内容。

  • 默认情况下,每个基准测试运行至少1秒。如果Benchmark函数返回时第二秒还没有过去,则b.N的值将按顺序1、2、5、10、20、50,…增加,然后函数再次运行
  • 最终的BenchmarkFib40只运行了两次,每次运行的平均值不到一秒钟。由于测试程序包使用简单的平均值(在b.N上运行基准函数的总时间),因此该结果在统计上较弱。您可以使用-benchtime标志增加最短​​基准时间,以产生更准确的结果。
go test -bench=Fib40 -benchtime=20s
# 结果如下
goos: linux
goarch: amd64
pkg: test/benchmark
BenchmarkFib40-4              50         680114858 ns/op
PASS
ok      test/benchmark  34.681s

误区

上面我提到了for循环对于基准驱动程序的运行至关重要。这是错误的基准测试的两个示例

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

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

BenchmarkFibWrong无法完成。这是因为基准测试的运行时间会随着b.N的增加而增加,而永远不会收敛于稳定值。 BenchmarkFibWrong2同样受到影响,并且永远不会完成