BenchmarkXxx函数必须满足三条件:首字母大写驼峰命名、参数为*testing.B、定义在同包的xxx_test.go中,否则go test -bench=.会静默跳过。

func BenchmarkXxx(b *testing.B) 必须这么写
不满足这三个条件,go test -bench=. 就会静默跳过——不报错、不提示、输出里压根没你的函数:
- 函数名必须是
BenchmarkXxx(首字母大写驼峰,不能小写、不能带Test前缀) - 参数必须是
*testing.B(带星号指针,写成testing.B会编译失败) - 必须定义在
xxx_test.go文件中,且和被测代码同包(不能放main包或独立测试包)
常见无效写法:benchmarkFib、Benchmarkfib、TestBenchmarkFib、func BenchmarkXxx(b testing.B)、放在 main.go 里。
for i := 0; i
b.N 是框架动态决定的整数,目标是让整个循环耗时接近 -benchtime(默认 1 秒)。它可能从 1 跳到 100 万甚至上亿——这是正常行为,说明框架在努力压出稳定样本。
你手动写死 for i := 0; i ,等于绕过整个基准机制,<code>ns/op 数值完全不可比、不可复现。
立即学习“go语言免费学习笔记(深入)”;
还要注意:
- 别在循环里做
if b.N == 1初始化——每次运行b.N都不同,逻辑必然错乱 - 被测逻辑结果必须被消费,比如赋给
_或全局变量,否则编译器可能直接优化掉整段计算 - 禁用
time.Now()手动计时,它绕过统计机制、引入额外开销、拿不到内存分配数据
b.ResetTimer() 和 b.ReportAllocs() 必须成对出现
初始化开销(如 make([]byte, 1e6)、json.Marshal 预热、打开文件)如果不剥离,就会被摊进 ns/op,导致结果是“初始化 + 执行”的混合值。尤其当初始化耗时远超主逻辑时,数字毫无参考性。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
标准结构是:
- 先做所有 setup(构造数据、预热缓存等)
- 调用
b.ResetTimer()—— 清空已计时间,重启计时器 - 再执行
for i := 0; i -
b.ReportAllocs()要放在b.ResetTimer()之后,否则内存统计含 setup 阶段,allocs/op和B/op失真
如果主逻辑有副作用(比如修改全局 map),每次迭代前需重置状态,否则数据污染影响结果。
对比两个实现必须用 benchstat,不能手比 ns/op
单独跑两次 go test -bench=. 再手动比数字,很容易因系统噪声(后台进程、GC 抖动、CPU 频率变化)得出错误结论。
正确做法:
- 先跑基线:
go test -bench=BenchmarkFoo -benchmem -count=5 | tee bench-old.txt - 改代码后跑新版本:
go test -bench=BenchmarkFoo -benchmem -count=5 | tee bench-new.txt - 用
benchstat bench-old.txt bench-new.txt(需先go install golang.org/x/perf/cmd/benchstat@latest)
benchstat 不是比较平均值,而是用 Welch’s t-test 判断差异是否统计显著,并给出置信区间。它能告诉你:“新实现快了 12%,p < 0.01”,而不是“看起来快一点”。
最容易被忽略的是:两次测试必须在相同 Go 版本、相同机器负载下运行;-count=5 不是越多越好,3–5 次足够,再多收益递减,还拖慢反馈周期。


















