跳转到主要内容
模块 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.txt

benchstat(在 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 ???
答案选项