基准测试

174 阅读4分钟

我们肯定都希望我们的代码既没有 bug ,性能又高,想要没有 bug 我们可以使用单元测试,那我们怎么评估我们代码的性能呢?这里就引出了基准测试。

什么是基准测试

基准测试(Benchmark)是一项用于测量和评估软件性能指标的方法,主要用于评估你写的代码的性能。

如何在 go 语言中进行基准测试

go 语言的基准测试与单元测试类似。

同样的我们先编写一个函数,这个函数的功能是求 n 的阶乘。

func Factorial(n int) int {
   // 负数没有阶乘,我们这里直接返回
   if n < 0 {
      return 0
   }

   // 0的阶乘为1
   if n == 0 {
      return 1
   }

   // 循环求阶乘
   fac := 1
   for i := 2; i <= n; i++ {
      fac *= i
   }
   return fac
}

基准测试与单元测试都是测试,对同一文件的测试我们都放在同一测试文件中。

这是一个非常简单的 go 语言基准测试示例,它和单元测试的不同点如下:

  1. 基准测试函数必须以 Benchmark 开头,必须是可导出的;
  2. 函数的签名必须接收一个指向 testing.B 类型的指针,并且不能返回任何值;
  3. 最后的 for 循环很重要,被测试的代码要放到循环里;
  4. b.N 是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能。

下面是一个基准测试函数的简单例子。

func BenchmarkFactorial(b *testing.B) {
   for i := 0; i < b.N; i++ {
      Factorial(10)
   }
}

执行以下命令

go test -bench=BenchmarkFactorial .

得到以下输出

goos: linux
goarch: amd64
pkg: context_demo
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkFactorial-16           279814405                4.299 ns/op
PASS
ok      context_demo    1.641s

想要进行基准测试只需要在go test后加上 -bench 的 flag ,它接受一个表达式作为参数,以匹配基准测试的函数,"."表示运行所有基准测试。

输出内容中函数名后面的 -16 代表运行基准测试时对应的 GOMAXPROCS 的值。接着的 279814405 表示运行 for 循环的次数,也就是调用被测试代码的次数,最后的 4.299 ns/op 表示每次需要花费 4.299 纳秒。

如果想让测试运行的时间更长,可以通过 -benchtime 指定,比如 3 秒,代码如下所示:

go test -bench=. -benchtime=3s .

更精确的计时

有时在测试函数中不止是对被测试函数的调用,还有一些代码也会消耗时间,这些时间不应该算在我们被测试函数的执行时间上,想要获得更精确的时间,我们就需要使用到一些方法来精确控制时间

具体包含以下三个方法:

func (b *B) StartTimer()

func (b *B) StopTimer()

func (b *B) ResetTimer()

使用这三个方法可以帮你灵活地控制什么时候开始计时、什么时候停止计时。

内存统计

在基准测试时,还可以统计每次操作分配内存的次数,以及每次操作分配的字节数,这两个指标可以作为优化代码的参考。要开启内存统计也比较简单,代码如下,即通过 ReportAllocs() 方法:

func BenchmarkFactorial(b *testing.B) {
   n := 10

   b.ReportAllocs() //开启内存统计
   b.ResetTimer()   //重置计时器
   for i := 0; i < b.N; i++ {
      Factorial(n)
   }
}

这时再执行测试命令,便会有以下输出:

goos: linux
goarch: amd64
pkg: context_demo
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkFactorial-16           464307045                2.570 ns/op           0 B/op          0 allocs/op
PASS
ok      context_demo    1.459s            

并发基准测试

并发作为 go 语言的一大特色,我们在进行基准测试时也可以使用并发的方式测试在多个 goroutine 并发下代码的性能。

func BenchmarkFactorial(b *testing.B) {
   n := 10

   b.RunParallel(func(pb *testing.PB) {
      for pb.Next() {
         Factorial(n)
      }
   })
}

使用 RunParallel 创建多个 goroutine ,并将 b.N 分配给这些 goroutine 执行。

这是再执行测试命令,得到以下输出:

goos: linux
goarch: amd64
pkg: context_demo
cpu: AMD Ryzen 7 6800H with Radeon Graphics
BenchmarkFactorial-16           1000000000               0.6325 ns/op
PASS
ok      context_demo    0.702s

可以看到运行时间大大降低。

总结

  • 我们可以通过基准测试来对自己代码性能做一个大概的分析。
  • 编写基准测试函数后使用 -bench这一 flag 来进行基准测试。

ps:go 语言还为我们提供了一个性能剖析的工具 pprof,通过它你可以查看 CPU 分析、内存分析、阻塞分析、互斥锁分析