必须命名为BenchmarkXXX且参数为*testing.B,放在_test.go文件中同包,禁用log.Fatal/panic,否则go test -bench会静默跳过或结果异常。

函数名和签名不合规,go test -bench 直接静默跳过
Go 不会报错,也不会提示,只要函数名不是 BenchmarkXXX(首字母大写、驼峰),或参数不是 *testing.B,go test -bench=. 就当它不存在。常见翻车点包括:benchmarkXXX(小写开头)、Benchmark_xxx(含下划线)、func BenchmarkXxx(t *testing.T)(错用 *testing.T)。
- 必须放在
_test.go文件里,且和被测代码同包 - 别在函数里调
log.Fatal或panic,否则测试中途退出,b.N会异常,结果不可信 - 验证是否生效:加个空循环
for i := 0; i ,再跑 <code>go test -bench=.,有输出就说明函数被识别了
b.N 是动态值,手写固定循环次数会让结果完全失真
b.N 不是常量,而是 Go 测试框架根据单次耗时自动调整的迭代次数,目标是让总运行时间稳定在约 1 秒左右。你写 for i := 0; i ,等于绕过整个基准机制——<code>b.N 失效,-benchtime 失效,不同机器、不同负载下结果波动极大。
- 永远用
for i := 0; i ,让框架掌控节奏 - 如果函数本身很慢(比如含 I/O 或
time.Sleep),框架可能把b.N调成 1 或 2,此时要加-benchmem看分配,而不是硬改循环 - 别在循环里做初始化(如反复
make([]int, 100)),这测的是分配 + 主逻辑,不是主逻辑本身
ns/op 和 B/op 要一起看,单独盯一个容易误判
ns/op 是每次操作平均纳秒数,B/op 是每次操作平均分配字节数。两者不必然同向变化:CPU 优化可能降低 ns/op,但引入更多临时变量会推高 B/op,进而抬升 GC 压力,在高频调用场景下反而拖垮整体吞吐。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 加
-benchmem才显示B/op和allocs/op,不加默认不统计 -
B/op为 0 不代表没分配,可能是逃逸分析成功,对象分配在栈上(用go build -gcflags="-m"可验证) - 若
B/op显著上升,优先检查fmt.Sprintf、strings.Builder.String()、返回局部切片等隐式堆分配点
b.ResetTimer() 放错位置,测的就不是你想测的东西
基准测试默认从函数入口开始计时。但初始化(如建 map、读文件、预热缓存)不该计入性能数据。漏掉 b.ResetTimer(),或者把它放在循环里、甚至放在循环之后,测出来的就是“初始化 + N 次调用”的混合耗时,尤其当初始化很重时,结果完全失真。
立即学习“go语言免费学习笔记(深入)”;
- 正确顺序:setup 代码 →
b.ResetTimer()→ 主循环 - 如果 setup 本身耗时长(比如启动 HTTP server),用
b.StopTimer()包住它,再b.ResetTimer() - 副作用函数(如修改全局变量)需在每次循环前重置状态,也得放在
b.StopTimer()区块内,否则后续轮次走捷径 - 别在循环里调
runtime.GC()—— 会把 GC 时间算进ns/op;真需要清理,只在b.ResetTimer()前调一次,并配time.Sleep(2 * time.Millisecond)等 GC 完成
真正难的不是让 Benchmark 跑起来,而是确保它测的是你以为自己在测的东西——尤其是涉及并发、缓存预热、GC 交互时,b.ResetTimer() 的位置、变量作用域、甚至编译器内联开关,都可能让结果偏离真实场景两个数量级。

















