go benchmark函数必须以benchmark开头且接收testing.b;常见错误是误用testing.t或拼错函数名,导致bench被静默忽略;需用b.resettimer()分离初始化,核心逻辑置于b.n循环内。

Go Benchmark 函数必须以 Benchmark 开头且接收 *testing.B
不满足这个签名,go test -bench 就完全找不到它——不是报错,是静默忽略。常见错误是写成 func BenchmarkFoo(t *testing.T)(用了 *testing.T),或者函数名拼错比如 BenckmarkFoo,结果跑完 go test -bench=. 显示 no benchmarks to run。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 函数名严格以
Benchmark大写开头,后接驼峰命名,如BenchmarkMapAccess - 参数必须是
b *testing.B,不能是t *testing.T - 在函数体内调用
b.ResetTimer()前置初始化逻辑(比如构造大数据结构),避免把 setup 时间算进基准耗时 - 核心被测逻辑必须放在
b.N循环里:func BenchmarkSort(b *testing.B) { data := make([]int, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { sort.Ints(data) // 这行会被执行 b.N 次 } }
为什么 b.N 不是固定次数,而由 Go 自动调整
b.N 是测试框架根据预设最低运行时间(默认 1 秒)动态决定的。它会先试跑少量次数,估算单次耗时,再反推需要多少次才能接近 1 秒——所以你不能假设 b.N == 1000 或硬编码循环次数。这也是为什么不能在循环里做一次性初始化(比如 make 新切片),否则每次迭代都新建内存,测的就不是目标逻辑而是分配开销。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有初始化(生成输入数据、打开文件、构建 map 等)必须放在
b.ResetTimer()之前 - 如果被测函数本身有副作用(比如修改入参),记得在循环内重新准备干净输入,否则后续迭代可能读到脏数据
- 想强制指定迭代次数?不行。但可以加
-benchtime=500ms缩短总时长,或-count=3多跑几轮取平均
go test -bench 默认不运行普通测试,也不显示内存分配
只跑 Benchmark 函数,Test 函数默认跳过;而且默认只输出时间/操作,不显示分配了多少对象、多少字节——这会让你错过关键瓶颈。比如一个函数快是因为缓存了结果,还是因为压根没分配?不看 alloc 找不到真相。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 加上
-benchmem参数,输出像5000000 322 ns/op 0 B/op 0 allocs/op这样的信息 - 用
-run=^$显式禁止任何Test函数执行,防止干扰:go test -bench=. -benchmem -run=^$ - 想对比两个分支?用
benchstat(需go install golang.org/x/perf/cmd/benchstat)处理多次输出,自动计算差异和 p 值
子基准测试(b.Run)适合横向比较不同实现
比如想比对 map[string]int 和 sync.Map 在并发读场景下的表现,或者不同 JSON 解析库的吞吐量。这时候不用写三个独立函数,用 b.Run 分组更清晰,还能共享 setup 逻辑,输出也带层级。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 子基准名是字符串,不要含空格或特殊字符,比如
b.Run("WithMutex", ...) - 每个
b.Run内部仍要遵守b.N循环 +b.ResetTimer()规则 - 注意:子基准的
b.N是各自独立调整的,不是父基准的b.N拆分——所以各子项实际运行次数可能不同 - 示例片段:
func BenchmarkJSONParse(b *testing.B) { data := loadSampleJSON() b.Run("encoding/json", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { var v map[string]interface{} json.Unmarshal(data, &v) } }) b.Run("json-iterator", func(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { var v map[string]interface{} jsoniter.Unmarshal(data, &v) } }) }
真正难的不是写对签名,而是确保每次迭代测的确实是你要优化的那一小段逻辑——初始化、副作用、GC 干扰、CPU 频率波动,全得手动排除。跑出数字只是开始,看懂它为什么是这个数,才刚上路。











