Benchmark函数必须放在_test.go文件里且以Benchmark开头、接收*testing.B参数,否则go test -bench=.会静默跳过;常见错误包括文件名不符、函数名大小写错误、参数类型错误;b.ResetTimer()须在耗时逻辑前调用以避免初始化开销污染结果。

Benchmark 函数必须放在 _test.go 文件里,且名字以 Benchmark 开头、参数为 *testing.B——否则 go test -bench=. 会完全跳过,不报错也不提示。
为什么 go test -bench=. 没跑任何测试?
最常见原因是函数或文件不满足硬性约束:
- 文件名不是
xxx_test.go(比如写成benchmark.go或utils_bench.go) - 函数名写成
benchmarkAdd(小写开头)、Benchmarkadd(第二个字母小写)或TestBenchmarkAdd(混用 Test 前缀) - 参数写成
testing.B(值类型)、*testing.T(错用单元测试类型)或多加了其他参数
用 go test -bench=. -v 能看到实际发现并执行了哪些函数,是第一排查手段。
如何避免初始化开销污染性能结果?
b.ResetTimer() 必须在耗时逻辑前调用,否则构造测试数据的时间会被计入最终 ns/op:
- 错误写法:数据初始化、map 预热、channel 创建等全放在循环外但没调用
b.ResetTimer() - 正确顺序:先准备数据 → 立即调用
b.ResetTimer()→ 再进入for i := 0; i 循环 - 注意:不能在循环内反复调用
b.ResetTimer(),它只应出现一次,且在被测逻辑开始前
怎么防止编译器优化掉被测代码?
Go 编译器可能直接删掉“无副作用”的计算,导致 ns/op 虚低甚至为 0:
立即学习“go语言免费学习笔记(深入)”;
- 把返回值赋给一个包级变量(如
var result interface{}),并在测试末尾引用它(result = r) - 不要用
_ = someFunc(),这仍可能被优化;必须让结果参与后续不可省略的表达式 - 对纯计算函数(如
Fibonacci(20)),尤其要加 blackhole 变量,否则基准结果毫无意义
如何解读和对比 benchmark 输出?
运行 go test -bench=. -benchmem 后,关键字段含义很实在:
-
ns/op:单次调用平均纳秒数,越小越好,但需结合分配看是否“省时间但烧内存” -
B/op:每次操作分配的字节数,直接影响 GC 压力;0 B/op是理想状态 -
allocs/op:每次操作的堆分配次数;高频小对象分配(如strings.Builder内部切片扩容)会推高该值 - 同一函数名后带
-8表示用了 8 个逻辑核;用-cpu=1,4,8可横向比并发扩展性
真正容易被忽略的是:b.N 不是固定值,它由框架动态调整以保证总耗时达标(默认 1 秒);所以两次运行的 b.N 可能差十倍,但 ns/op 才是可比指标——别盯着循环次数看。



















