1、基本使用

基准测试常用于代码性能测试,函数需要导入testing包,并定义以Benchmark开头的函数, 参数为testing.B指针类型,在测试函数中循环调用函数多次

go test testcalc/calc -bench .
go test testcalc/calc -bench . -run=none
# 显示内存信息
go test testcalc/calc -bench . -benchmem
go test -bench=. -benchmem -run=none

go test会在运行基准测试之前之前执行包里所有的单元测试,所有如果你的包里有很多单元测试,或者它们会运行很长时间,你也可以通过go test-run标识排除这些单元测试

业务代码fib.go,测试斐波那契数列

package pkg06

func fib(n int) int {
	if n == 0 || n == 1 {
		return n
	}
	return fib(n-2) + fib(n-1)
}

测试代码fib_test.go

package pkg06

import "testing"

func BenchmarkFib(b *testing.B) {
	for n := 0; n < b.N; n++ {
		fib(30)
	}
}

执行测试

➜  go test -bench=. -run=none
goos: darwin
goarch: amd64
pkg: pkg06
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkFib-12              250           4682682 ns/op
PASS
ok      pkg06   1.875s
➜  go test -bench=. -benchmem -run=none
goos: darwin
goarch: amd64
pkg: pkg06
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkFib-12              249           4686452 ns/op               0 B/op          0 allocs/op
PASS
ok      pkg06   1.854s

2、bench的工作原理

  • 基准测试函数会被一直调用直到b.N无效,它是基准测试循环的次数
  • b.N1开始,如果基准测试函数在1秒内就完成 (默认值),则b.N增加,并再次运行基准测试函数
  • b.N的值会按照序列1,2,5,10,20,50,...增加,同时再次运行基准测测试函数
  • 上述结果解读代表1秒内运行了250次,每次4682682 ns
  • -12后缀和用于运行次测试的GOMAXPROCS值有关。 与GOMAXPROCS一样,此数字默认为启动时Go进程可见的CPU数。 可以使用-cpu标识更改此值,可以传入多个值以列表形式来运行基准测试

3、传入cpu num进行测试

➜  go test -bench=. -cpu=1,2,4  -benchmem -run=none
goos: darwin
goarch: amd64
pkg: pkg06
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkFib                 244           4694667 ns/op               0 B/op          0 allocs/op
BenchmarkFib-2               255           4721201 ns/op               0 B/op          0 allocs/op
BenchmarkFib-4               256           4756392 ns/op               0 B/op          0 allocs/op
PASS
ok      pkg06   5.826s

4、count多次运行基准测试

因为热缩放、内存局部性、后台处理、gc活动等等会导致单次的误差,所以一般会进行多次测试

➜  go test -bench=. -count=10  -benchmem -run=none 
goos: darwin
goarch: amd64
pkg: pkg06
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkFib-12              217           5993577 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              246           5065577 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              244           4955397 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              255           4689529 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              254           4879802 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              254           4691213 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              255           4772108 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              240           4724141 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              255           4717087 ns/op               0 B/op          0 allocs/op
BenchmarkFib-12              255           4787803 ns/op               0 B/op          0 allocs/op
PASS
ok      pkg06   18.166s

5、benchtime指定运行秒数

有的函数比较慢,为了更精确的结果,可以通过-benchtime标志指定运行时间,从而使它运行更多次

➜  go test -bench=. -benchtime=5s  -benchmem -run=none
goos: darwin
goarch: amd64
pkg: pkg06
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkFib-12             1128           4716535 ns/op               0 B/op          0 allocs/op
PASS
ok      pkg06   7.199s

6、ResetTimer重置定时器

可能在真正测试之前还需要做很多例如初始化等工作,这时可以在需要测试的函数执行之初添加一个重置定时器的功能,这样最终得到的时间就更为精确

package pkg06

import (
	"testing"
	"time"
)

func BenchmarkFib(b *testing.B) {
	time.Sleep(3 * time.Second)
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		fib(30)
	}
}

执行测试

➜  go test -bench=. -benchtime=5s  -benchmem -run=none
goos: darwin
goarch: amd64
pkg: pkg06
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkFib-12             1239           4712413 ns/op               0 B/op          0 allocs/op
PASS
ok      pkg06   16.122s

7、benchmem展示内存消耗

  • 例如测试大cap的切片,直接用cap初始化和cap动态扩容进行对比
package pkg07

import (
	"math/rand"
	"testing"
	"time"
)

// 指定大的cap的切片
func generateWithCap(n int) []int {
	rand.Seed(time.Now().UnixNano())
	nums := make([]int, 0, n)
	for i := 0; i < n; i++ {
		nums = append(nums, rand.Int())
	}
	return nums
}

// 动态扩容的slice
func generateDynamic(n int) []int {
	rand.Seed(time.Now().UnixNano())
	nums := make([]int, 0)
	for i := 0; i < n; i++ {
		nums = append(nums, rand.Int())
	}
	return nums
}

func BenchmarkGenerateWithCap(b *testing.B) {
	for n := 0; n < b.N; n++ {
		generateWithCap(100000)
	}
}

func BenchmarkGenerateDynamic(b *testing.B) {
	for n := 0; n < b.N; n++ {
		generateDynamic(100000)
	}
}

执行测试

➜  go test -bench=. -benchmem -run=none 
goos: darwin
goarch: amd64
pkg: pkg07
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkGenerateWithCap-12          672           1729465 ns/op          802817 B/op          1 allocs/op
BenchmarkGenerateDynamic-12          561           2122992 ns/op         4654346 B/op         30 allocs/op
PASS
ok      pkg07   3.777s

结论:用cap初始化好的性能可以高一个数据量级

  • 例如测试测试函数复杂度,不带capslice动态扩容

对上面代码中调用动态扩容生成切片进行再次封装

package pkg08

import (
	"math/rand"
	"testing"
	"time"
)

// 指定大的cap的切片
func generateWithCap(n int) []int {
	rand.Seed(time.Now().UnixNano())
	nums := make([]int, 0, n)
	for i := 0; i < n; i++ {
		nums = append(nums, rand.Int())
	}
	return nums
}

// 动态扩容的slice
func generateDynamic(n int) []int {
	rand.Seed(time.Now().UnixNano())
	nums := make([]int, 0)
	for i := 0; i < n; i++ {
		nums = append(nums, rand.Int())
	}
	return nums
}

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

func BenchmarkGenerateDynamic1000(b *testing.B)     { benchmarkGenerate(1000, b) }
func BenchmarkGenerateDynamic10000(b *testing.B)    { benchmarkGenerate(10000, b) }
func BenchmarkGenerateDynamic100000(b *testing.B)   { benchmarkGenerate(100000, b) }
func BenchmarkGenerateDynamic1000000(b *testing.B)  { benchmarkGenerate(1000000, b) }
func BenchmarkGenerateDynamic10000000(b *testing.B) { benchmarkGenerate(10000000, b) }

执行测试

➜  go test -bench=. -benchmem -run=none 
goos: darwin
goarch: amd64
pkg: pkg08
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz
BenchmarkGenerateDynamic1000-12            39540             26557 ns/op           16376 B/op         11 allocs/op
BenchmarkGenerateDynamic10000-12            5452            210894 ns/op          386296 B/op         20 allocs/op
BenchmarkGenerateDynamic100000-12            572           2106325 ns/op         4654341 B/op         30 allocs/op
BenchmarkGenerateDynamic1000000-12            48          23070939 ns/op        45188416 B/op         40 allocs/op
BenchmarkGenerateDynamic10000000-12            5         212567041 ns/op        423503110 B/op        50 allocs/op
PASS
ok      pkg08   9.686s

结论:输入变为原来的10倍,单次耗时也差不多是上一级的10倍。说明这个函数的复杂度是接近线性的