go基准测试需满足四条件:文件名以_test.go结尾且同包;函数名以benchmark开头并接合法标识符;签名必须为func benchmarkxxx(b *testing.b);主体用b.n循环。

Go 的基准测试不是调用某个框架 API,而是直接用标准库 testing 包 + go test -bench 命令驱动的原生机制——没有第三方“Benchmark 框架”需要安装或引入,硬加反而容易破坏计时逻辑。
怎么写一个能被 go test -bench 扫到的函数
它必须同时满足四个硬性条件,缺一不可:
- 文件名以
_test.go结尾,且和被测代码同包 - 函数名以
Benchmark开头(首字母大写),后面接合法标识符,比如BenchmarkJSONUnmarshal - 签名严格为
func BenchmarkXxx(b *testing.B),不能多参、少参,也不能是*testing.T - 主体循环必须用
for i := 0; i ,不能写死次数(如 <code>for i := 0; i )
常见错误现象:运行 go test -bench=. 输出 no benchmarks to run 或静默跳过。90% 是函数签名写成 func BenchmarkXxx(t *testing.T),或文件没叫 xxx_test.go。
为什么不能手动控制循环次数
b.N 不是固定值,而是 Go 测试框架根据预跑耗时动态算出来的迭代次数,目标是让整个函数总执行时间 ≥ 1 秒(默认)。你写死循环,会导致:
- 单次太慢 → 总耗时远超 1 秒,
ns/op被拉低,失真 - 单次太快 → 总耗时不足 1 秒,框架不会自动放大
b.N,结果波动极大 -
-benchtime=5s等参数完全失效
正确做法永远只写 for i := 0; i ,把节奏交给框架。它最终输出的 <code>ns/op 才具备跨环境、跨机器的可比性。
如何隔离 setup 和 cleanup 开销
打开文件、初始化 map、构建请求对象这些操作,如果混在循环里,会被计入耗时,污染结果。
- setup(如
data, _ := os.ReadFile("test.json"))放b.ResetTimer()之前 - 主逻辑放
for i := 0; i 里 - cleanup(如
os.Remove("tmp.db"))放循环之后、b.StopTimer()之后
注意:b.Run 嵌套子 benchmark 时,父函数的 ResetTimer 对子函数无效,每个子 benchmark 必须自己调 b.ResetTimer()。
怎么让内存分配统计生效
-benchmem 参数只是开启内存统计开关,但 allocs/op 显示为 0,往往是因为漏了 b.ReportAllocs()。
- 必须在
for循环之前调用b.ReportAllocs() - 它不改变行为,只告诉框架:“请记录接下来
b.N次循环里的内存分配” - 如果放在循环里或循环后,等于没开,
allocs/op永远是 0
哪怕被测逻辑不分配内存,也建议默认加上——避免误判,且无副作用。
最易被忽略的点:编译器可能把未使用的计算结果整个优化掉,比如 result := f(x) 后没读 result。解决方法很简单:_ = result 或 blackhole(result)(实际只需赋值再丢弃),否则你测的根本不是真实逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











