Go基准测试函数须以Benchmark开头、参数为*testing.B,初始化放b.ResetTimer()前,被测逻辑置于for循环内,且必须用go test -bench执行。

基准测试函数怎么写才不“测歪”
Go 的 Benchmark 函数不是写个循环计时就行,它必须由 go test -bench 驱动,否则 b.N 不生效、计时不准确、内存统计失效。常见错误是手动用 time.Now() 测,或者在循环里反复初始化数据。
- 函数名必须以
Benchmark开头,参数类型严格为*testing.B - 所有初始化(如构造切片、读文件、生成测试字符串)必须放在
b.ResetTimer()之前 - 被测逻辑必须包裹在
for i := 0; i 中,不能硬编码次数(如for i := 0; i ) - 结果必须被“使用”,例如
_ = someFunc()或赋值给全局变量,否则编译器可能整个优化掉
运行命令加哪些 flag 才算靠谱
默认 go test -bench=. 只跑约 1 秒,容易受 CPU 频率波动、GC 突发、单次抖动干扰。生产级对比至少要控制三件事:时间稳定性、内存可见性、重复可信度。
-
-benchmem:强制显示B/op和allocs/op,不加这个就等于没看内存——很多性能问题藏在分配次数里 -
-benchtime=5s:让每个 benchmark 至少跑 5 秒,显著降低单次误差 -
-count=3:重复执行 3 次取中位数,benchstat才能做统计对比 - 组合推荐:
go test -bench=^BenchmarkFoo$ -benchmem -benchtime=5s -count=3
怎么用 pprof 定位真正的瓶颈
看到 ns/op 下降了 20%,不代表优化有效——可能只是把开销从 CPU 转移到 GC 或锁竞争上。pprof 不是用来“确认变快了”,而是回答“时间到底花在哪了”。
- 先在测试文件里导入
_ "net/http/pprof",再启一个 goroutine:go http.ListenAndServe("localhost:6060", nil) - 运行 benchmark 同时执行:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30 - 进交互后别急着看
top,先用list 函数名确认热点是否真在你改的那几行;再用web看火焰图,检查有没有意外的 interface{} 转换或未内联小函数 - 如果
top显示大量 time.Sleep 或 runtime.mcall,说明你在测的可能是调度器开销,不是业务逻辑
对比两个实现时最容易漏掉的控制点
想比 strings.Builder 和 +=,只写两个 Benchmark 函数还不够。环境不对等,结果就不可信。
立即学习“go语言免费学习笔记(深入)”;
- 两组测试必须用完全相同的输入(比如同一份
[]byte或字符串常量),不能一个用"a",一个用fmt.Sprintf("a") - 避免复用可变对象:比如
bytes.Buffer在第一次迭代后没Reset(),后续迭代会带着旧内容,测的是“追加”而非“构建” - 子测试更安全:
b.Run("builder", func(b *testing.B){...})自动隔离状态,还能单独运行:go test -bench=Builder - 如果其中某个实现触发了逃逸分析(如返回局部指针),
allocs/op会飙升——这时得用go build -gcflags="-m"看逃逸报告,而不是只盯ns/op
go tool pprof http://localhost:6060/debug/pprof/heap 看堆增长趋势。











