Go基准测试需严格遵循规范:函数名以Benchmark开头、参数为*testing.B且唯一、b.N循环在函数体内;否则会被静默跳过或编译失败,导致性能数据失真。

Go 基准测试不是“写个 BenchmarkXXX 函数就能跑出准数”,它极易因初始化位置、结果未使用、GC 干扰或参数误设,导致 ns/op 失真、allocs/op 虚高,甚至测的压根不是你想测的逻辑。
函数签名和命名必须严格合规
Go 的 go test -bench=. 只识别满足三个条件的函数:名字以 Benchmark 开头(如 BenchmarkJSONUnmarshal)、参数类型是 *testing.B(不能漏掉 *)、且仅有一个参数。任何偏差都会被静默跳过——go test -v 里根本不会出现它。
-
BenchmarkjsonUnmarshal(小写 j)→ 不识别 -
func BenchmarkFoo(b testing.B)(值传递)→ 编译失败 -
func BenchmarkFoo(b *testing.B, extra int)(多参数)→ 不识别
b.N 循环必须在函数体里,且不能硬编码
b.N 是 Go 自动调节的迭代次数,目标是让总耗时趋近于 1 秒(可由 -benchtime 调整)。你必须用 for i := 0; i 包裹被测逻辑,而不是写死 <code>for i := 0; i 。
- 硬编码会导致结果不可比:不同机器、不同 Go 版本下,100 万次执行时间波动极大
-
b.N在每次运行中可能变化,但保证了单次ns/op具有统计意义 - 如果被测函数本身含循环(比如批量处理 slice),应把整个批量操作视为“一次操作”,不要在内层再套
b.N
初始化代码必须用 b.ResetTimer() 隔离
构造测试数据(如 make([]byte, 1e6))、预热缓存、打开文件等,都不该计入性能指标。默认计时从函数入口开始,所以这些操作要放在 b.ResetTimer() 之前。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
data := make([]byte, 1e6); b.ResetTimer(); for ...→make耗时被计入 - 正确写法:
data := make([]byte, 1e6); b.ResetTimer(); for ...→make不计时,只测后续循环 - 若初始化本身也是你要评估的一部分(比如 map 首次构建开销),那就别调
b.ResetTimer(),但需在注释里明确说明
防止编译器优化和 GC 干扰
如果被测函数返回值没被使用,Go 编译器可能直接删掉整段逻辑;频繁小对象分配还会触发 GC,让 ns/op 波动剧烈、不可复现。
- 用
_ = result或blackhole = result(声明为包级变量)保留结果,避免优化 - 加
b.ReportAllocs()和b.SetBytes(n)(传单次操作字节数)在循环前调用,否则无效 - 怀疑 GC 干扰?加
GODEBUG=gctrace=1 go test -bench=. -run=^$观察 GC 次数是否随b.N线性增长 - 字符串拼接优先用
strings.Builder或预分配[]byte,别用+=
最易被忽略的其实是子测试分组和动态生成——当你要对比 5 种序列化方式、3 种输入规模、2 种配置时,手写 30 个 Benchmark 函数既难维护又容易漏掉某组组合。b.Run 能结构化输出,而 testing.Main + testing.InternalBenchmark 才是真正 scalable 的解法,但它要求你绕过静态函数扫描,自己组装测试列表,稍不注意就会卡在 os.Exit() 后无法调试。

















