跳到主要内容

Benchmark 基准测试

问题

Go 中如何编写和执行 Benchmark?如何正确解读结果?

答案

基本 Benchmark

// fib_test.go
func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(20)
}
}
go test -bench=BenchmarkFib -benchmem ./...

# 输出
BenchmarkFib-8 234567 5102 ns/op 0 B/op 0 allocs/op
# │ │ │ │ │
# CPU 核数 运行次数 每次耗时 每次内存 每次分配次数

常用命令

# 运行所有 Benchmark
go test -bench=. -benchmem ./...

# 指定运行时间(默认 1s)
go test -bench=. -benchtime=5s

# 多次运行取统计值
go test -bench=. -count=5 | tee old.txt

# 对比两次结果
go install golang.org/x/perf/cmd/benchstat@latest
benchstat old.txt new.txt

避免编译器优化

// ❌ 编译器可能优化掉未使用的结果
func BenchmarkBad(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(20) // 结果没被使用,可能被优化掉
}
}

// ✅ 将结果赋值给包级变量
var result int

func BenchmarkGood(b *testing.B) {
var r int
for i := 0; i < b.N; i++ {
r = Fib(20)
}
result = r
}

Table-Driven Benchmark

func BenchmarkFib(b *testing.B) {
cases := []struct {
name string
n int
}{
{"Fib10", 10},
{"Fib20", 20},
{"Fib30", 30},
}

for _, tc := range cases {
b.Run(tc.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
Fib(tc.n)
}
})
}
}

重置计时器

func BenchmarkWithSetup(b *testing.B) {
// 准备数据(不计入 Benchmark 时间)
data := generateLargeData()
b.ResetTimer() // 重置计时器

for i := 0; i < b.N; i++ {
process(data)
}
}

常见面试问题

Q1: b.N 是怎么确定的?

答案:Go 测试框架自动调整。先 b.N=1 跑一次估算耗时,然后按线性增长(100、10000...)直到运行时间 ≥ 1 秒。不需要手动设置。

Q2: -benchmem 输出的 allocs/op 意味着什么?

答案:每次操作的堆内存分配次数。这个数字越小越好。如果为 0 说明没有堆分配(全在栈上),性能最优。

相关链接