go基准测试函数必须满足四要素:文件名以_test.go结尾、函数名以benchmark开头、参数为testing.b类型、循环体使用b.n;缺一则go test -bench会忽略,常见错误是签名写成testing.t或文件名不符。

怎么写一个能跑起来的 Benchmark 函数
Go 的基准测试不是靠手动计时,而是必须用 testing.B 类型参数、函数名以 Benchmark 开头、放在 _test.go 文件里——少一个条件,go test -bench 就直接忽略它。
常见错误现象:go test -bench=. 输出空白,或提示 no benchmarks to run。大概率是函数签名不对(比如写了 func BenchmarkX(t *testing.T)),或者文件没叫 xxx_test.go。
- 函数签名必须是
func BenchmarkX(b *testing.B) - 循环体必须套在
b.N里:for i := 0; i - 别在循环里调
b.ResetTimer()或b.StopTimer()除非真需要排除 setup 开销 - 如果被测代码依赖初始化(如打开文件、构建 map),放循环外,否则会重复执行干扰结果
go test -bench 参数怎么选才不误导
默认 go test -bench=. 会跑所有 Benchmark 函数,但默认只跑 1 秒,自动调整 b.N 到“刚好撑满 1 秒”。这会导致不同函数的 b.N 差异极大,直接比 ns/op 数值可能失真——尤其当某个 benchmark 初始化慢、单次耗时长时。
使用场景:想横向对比两个算法,得让它们在相近的迭代规模下跑,而不是各自“适配”1秒。
立即学习“go语言免费学习笔记(深入)”;
- 加
-benchmem看内存分配(Benchmem会触发 GC,但不加就看不到allocs/op) - 用
-benchtime=5s延长总时长,减少计时抖动影响 - 强制统一迭代次数:加
-count=1避免多次运行取平均(有时反而是噪音源),再配合-benchmem - 慎用
-cpu:并发 benchmark(如b.RunParallel)才需要,普通函数设了也没用
为什么 b.ReportAllocs() 要手动调,而且得在循环前
b.ReportAllocs() 不是开关,而是“告诉测试框架:请记录本次运行的内存分配”。但它只对之后的 b.N 循环生效;如果写在循环里或循环后,等于没开。
容易踩的坑:忘了调,或者调晚了,结果 allocs/op 显示为 0,误以为没分配内存——其实只是没统计。
- 必须放在
for i := 0; i 循环之前 - 它本身不改变行为,只开启采样;没有它,
-benchmem仍会输出内存数据,但allocs/op恒为 0 - 如果被测逻辑本身不分配(比如纯计算),
ReportAllocs也无害,建议默认加上
func BenchmarkFoo(b *testing.B) {
b.ReportAllocs() // ✅ 这里
for i := 0; i < b.N; i++ {
_ = someFunc()
}
}如何避免微基准测试(micro-benchmark)掉进编译器优化陷阱
Go 编译器可能把看似“被测”的代码整个删掉——特别是返回值没用、中间变量没读、或者函数被内联后发现无副作用。这时你看到的 0 ns/op 或极低数值,不是快,是压根没跑。
性能影响:你优化了半天,实际线上根本没生效;或者对比 A/B 版本时,其中一个被优化掉了,结论完全错误。
- 确保关键结果被“逃逸”:用
blackhole变量接住返回值,再扔给runtime.KeepAlive或直接打印(仅调试) - 禁用内联测试函数:
//go:noinline放在被测函数上(不是 benchmark 函数) - 检查汇编:
go tool compile -S xxx.go | grep "someFunc",确认调用存在 - 简单验证法:在被测代码末尾加
if false { println("x") },看数值是否突变——突变说明原来被优化了
最麻烦的点往往不在写法,而在你根本没意识到编译器动了手。跑出异常低的数字时,第一反应不该是“我优化成功了”,而是“它还在执行吗?”











