go test -bench=. 仅识别满足三条件的函数:名以benchmark开头且驼峰大写、参数为*testing.b、文件名以_test.go结尾;b.n为动态迭代次数,须用for i := 0; i
函数名和签名不合法,
go test -bench=.就会静默跳过不是所有叫
Benchmark开头的函数都能被识别——必须同时满足三个硬性条件:函数名是BenchmarkXxx(首字母大写、驼峰)、参数类型是*testing.B(带星号指针)、文件名以_test.go结尾。漏掉任意一个,go test -bench=.既不报错也不提示,直接当没这回事。常见错误现象:
no tests to run或输出里压根没你的函数名;或者显示benchmarked 0 times。
BenchmarkFibonacci✅ 合法;benchmark_fib、TestBenchmarkFib、Benchmarkfib❌ 全部无效- 签名必须是
func BenchmarkXxx(b *testing.B);写成func BenchmarkXxx(b testing.B)(漏指针)或func BenchmarkXxx(b *testing.T)(类型错)都会失效- 别把基准测试写在
main.go或普通.go文件里——必须放在同包的xxx_test.go中
b.N是动态值,不能手写固定循环次数
b.N不是你能控制的常量,而是 Go 测试框架根据实际耗时自动调整的迭代次数。它的目标是让整个基准函数运行时间接近默认的 1 秒(可用-benchtime=3s调整)。你手动写for i := 0; i 不仅让 <code>b.N失效,还会导致结果不可比、CI 环境下波动剧烈。
- 正确写法永远是
for i := 0; i ,把待测逻辑放进去- 如果函数极快(比如纳秒级),
b.N可能飙升到千万甚至上亿——这是正常行为,说明框架在努力稳定测量- 不要用
if b.N == 1做一次性初始化——每次运行b.N都可能不同,逻辑会错乱初始化开销必须用
b.ResetTimer()剥离,否则ns/op完全失真像
make(map[int]int, 1e6)、json.Marshal预热数据、打开文件这类操作,如果混在计时区内,就会把 setup 时间摊到每次操作上,ns/op变成“初始化 + 执行”的混合值,尤其当初始化远重于主逻辑时,结果毫无参考意义。
- 标准结构:setup 代码 →
b.ResetTimer()→for i := 0; i { 主逻辑 }- 如果主逻辑有副作用(比如修改全局 map),每次迭代前需重置状态,也得放在
b.StopTimer()区块内,再b.StartTimer()b.ReportAllocs()要在b.ResetTimer()之后调用,否则内存统计包含 setup 阶段不强制使用返回值,编译器可能直接优化掉整个调用
Go 编译器对无副作用代码极其激进:如果你测的是
fib(40)却没接返回值,或者接了又没用,它可能连函数体都不生成——汇编里根本找不到对应逻辑,ns/op接近 0 不代表快,是压根没跑。真正难的不是写个能跑的
- 最简方案:加一行
blackhole := yourFunc(),再加blackhole = blackhole(防后续优化)- 更稳妥:用
runtime.KeepAlive(blackhole)或赋值给包级变量(注意别引入锁或 GC 干扰)- 验证是否被优化:加
go test -gcflags="-l" -bench=.关闭内联,看结果是否突变;但日常不用总关,重点是确保结果被消费BenchmarkXxx,而是意识到每行代码都在影响计时边界、逃逸分析和编译器判断——哪怕多一个变量声明、少一次ResetTimer(),都可能让ns/op偏差几倍。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!












