Golang基准测试
目录
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.N
从1
开始,如果基准测试函数在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
初始化好的性能可以高一个数据量级
- 例如测试测试函数复杂度,不带
cap
的slice
动态扩容
对上面代码中调用动态扩容生成切片进行再次封装
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
倍。说明这个函数的复杂度是接近线性的
See you ~