模块 9 · 第 3 课(共 5)课程中的43/50~12 min
模块课程(3/5)
基准和配置文件
除了功能测试之外,go test 还具有内置基准 (BenchmarkXxx) 并支持 CPU 和内存配置文件 (pprof)。一切都没有外部依赖。
基准的形状
Go
func BenchmarkSum(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = Sum(2, 3)
}
}特征:
- 确切的签名:
func BenchmarkXxx(b *testing.B)。 - 循环必须运行
b.N迭代:框架自动调整b.N,从 1 开始并不断增长,直到测量稳定(默认情况下约为 1 秒)。 - 结果是
ns/op(每次操作纳秒),并且与-benchmem一起,还有B/op和allocs/op。
典型命令
Bash
go test -bench=. ./... # all benchmarks
go test -bench=Sum -benchmem # only Sum, with memory info
go test -bench=. -benchtime=5s # each benchmark for 5 seconds
go test -bench=. -cpuprofile=cpu.out # generate a CPU profile典型输出:
Code
BenchmarkSum-8 1000000000 0.31 ns/op
-8 是 GOMAXPROCS 值。 0.31 ns/op 是每次调用的平均时间。
昂贵的设置:b.ResetTimer
如果循环之前有很长的准备步骤,请将其从测量中排除:
Go
func BenchmarkSearch(b *testing.B) {
data := buildLargeSlice(1_000_000) // expensive
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Search(data, 42)
}
}有用的变体:
b.StopTimer()/b.StartTimer()— 在无趣的阶段暂停测量(在循环内很方便)。b.ReportAllocs()— 即使没有-benchmem也强制分配报告。
带有 b.Run 的基准表
就像测试的 t.Run 一样,参数基准测试也有 b.Run :
Go
func BenchmarkSplit(b *testing.B) {
for _, size := range []int{10, 1000, 100000} {
b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
s := strings.Repeat("a,", size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = strings.Split(s, ",")
}
})
}
}输出行如 BenchmarkSplit/n=10-8、BenchmarkSplit/n=1000-8、... 非常适合缩放图表。
使用 benchstat 比较两个实现
Bash
go test -bench=. -count=10 > old.txt
# apply change
go test -bench=. -count=10 > new.txt
benchstat old.txt new.txtbenchstat(在 golang.org/x/perf/cmd/benchstat 中)计算差异的平均值、标准差和统计显着性。
练习
锻炼#go.m9.l3.e1
尝试:0加载中...
使用 b.N 上调用 Sum(2, 3) 的循环定义 BenchmarkSum。
正在加载编辑器...
3 次尝试后可用的解决方案
锻炼#go.m9.l3.e2
尝试:0加载中...
在昂贵的设置之后、循环之前添加 b.ResetTimer,因此设置不包含在测量中。
正在加载编辑器...
3 次尝试后可用的解决方案
Quiz#go.m9.l3.e3
准备好哪个标志运行包中的所有基准测试?
Go
$ go test ???