benchmark函数须以benchmark开头且签名固定为func(b *testing.b),否则被忽略;需用b.n控制循环次数,调用b.resettimer()排除初始化开销。

Benchmark 函数必须以 Benchmark 开头,且签名固定为 func(b *testing.B),否则 go test -bench 会直接忽略它。
如何让 Benchmark 正确运行并避免常见误判
Go 的基准测试不是“跑一次看耗时”,而是自动多次执行并取统计均值。但如果你在循环里没调用 b.N,或者手动写死迭代次数,结果就完全失真。
-
b.ResetTimer()要放在初始化代码之后、主循环之前,否则 setup 时间会被计入耗时 - 不要在
for i := 0; i 循环内做任何与被测逻辑无关的分配(比如反复 <code>make([]int, 100)),这会污染内存和 GC 行为 - 如果被测函数有副作用(如修改全局变量、写文件),需在每次循环中重置状态,否则后续轮次可能走捷径
- 用
go test -bench=. -benchmem同时观察分配次数和字节数,比单纯看 ns/op 更能定位性能瓶颈
为什么 b.ReportAllocs() 有时显示 0 B/op 却仍有内存增长
这是因为 Go 编译器可能把小对象分配优化到栈上(escape analysis 成功),testing.B 统计不到栈分配。但若函数逃逸(例如返回局部切片、传给接口{}),就会触发堆分配。
- 用
go build -gcflags="-m -l"检查关键变量是否逃逸 -
runtime.ReadMemStats()可在循环前后手动采样,比-benchmem更细粒度 - 注意:
fmt.Sprintf、strings.Builder.String()等常见操作极易触发隐式分配,应优先用预分配的bytes.Buffer或池化对象
多个 Benchmark 之间如何共享 setup 逻辑而不影响结果
不能靠包级变量或 init 函数——它们只执行一次,无法隔离各 Benchmark 的状态;也不能在每个 BenchmarkXxx 里重复写 setup,容易漏掉 b.ResetTimer() 位置。
- 推荐把 setup 封装成闭包,返回可复用的 clean-up 函数:
func makeTestEnv() (cleanup func(), err error) { tmpDir, _ := os.MkdirTemp("", "bench-") return func() { os.RemoveAll(tmpDir) }, nil } - 在 Benchmark 函数开头调用:
cleanup := makeTestEnv() defer cleanup()
- 务必确认
cleanup()不会阻塞或引入可观测延迟(比如等 goroutine 结束),否则要改用b.StopTimer()/b.StartTimer()包裹
真正难的不是写完一个能跑的 Benchmark,而是确保它测的是你**以为自己在测的东西**——尤其是涉及并发、缓存、GC 交互时,微小的 setup 顺序或变量作用域变化,就能让结果偏离真实场景两个数量级。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











