go test -bench 跑不出结果是因为基准测试函数命名或签名不合法:必须以 benchmark 开头且参数为 testing.b;若写成 testing.b 或 testing.t,会静默跳过;还需在函数体内使用 b.n 控制循环。

go test -bench 跑不出结果?检查测试函数命名和签名
Go 的基准测试不是写个 func BenchmarkXxx 就能自动跑起来的——它必须严格满足两个条件:函数名以 Benchmark 开头,且签名是 func BenchmarkXxx(*testing.B)。漏掉星号 * 或写成 testing.B(没指针),go test -bench=. 会静默跳过,不报错也不输出。
- 常见错误现象:
go test -bench=.返回空,或只显示ok ./pkg 0.001s,没任何BenchmarkXXX-8行 - 别用
func BenchmarkXxx(t *testing.T)混淆单元测试和基准测试 - 函数体里必须调用
b.N控制循环次数,比如for i := 0; i ;否则即使跑起来,耗时也恒为 0 - 如果测试文件里同时有
TestXxx和BenchmarkXxx,默认go test只跑单元测试;加-bench=.才触发基准测试
为什么本地跑的 ns/op 和 CI 机器差 3 倍?关掉 CPU 频率调节和后台干扰
Go 基准测试对系统环境极其敏感。ns/op 不是绝对值,而是多次运行后取稳定区间拟合出的估算值。Linux 上若 CPU 频率动态缩放(如 ondemand governor),单次迭代耗时抖动剧烈,go test 会反复重试、延长总耗时,甚至放弃采样,最终输出不可比的结果。
- 实操建议:Linux 下临时切到性能模式:
sudo cpupower frequency-set -g performance - macOS 用户注意:
pmset -a reducespeed 0并不够,还要关掉 Spotlight 索引、iTerm 多窗口渲染、Chrome 后台标签页 - 别在笔记本电池模式下跑基准测试——哪怕只是看趋势,也建议插电并设为“高能模式”
-
go test -bench=. -benchmem -count=5跑 5 轮取中位数,比单轮更抗干扰;但前提是每轮环境尽量一致
如何让 Benchmark 复用已有逻辑而不污染生产代码?用 _test.go + 内部函数
你不能把业务逻辑直接塞进 BenchmarkXxx 函数里——那样会导致测试代码被编译进正式二进制,还可能因未导出变量引发链接失败。正确做法是把待测逻辑抽成包内可访问的函数(哪怕未导出),在 *_test.go 文件里调用。
- 例如:待测的是
ParseJSON([]byte) (map[string]interface{}, error),就把它从func parseJSON(...)改为func parseJSON(...)(小写开头,同包内可调) - 别在
BenchmarkXxx里做初始化(如打开文件、构建大结构体)放在b.ResetTimer()前;否则初始化时间会计入ns/op - 如果初始化开销大且固定,用
b.StopTimer()/b.StartTimer()包裹真正要测的热路径 - 避免在循环内分配新对象(如每次
make([]int, N));提前分配好复用,否则-benchmem会暴露大量额外内存分配
go test -benchmem 显示 allocs/op 很高?检查是否意外逃逸到堆上
allocs/op 高通常意味着局部变量被编译器判定为“需要在堆上分配”,常见于返回局部切片、闭包捕获大变量、或传参时发生隐式复制。这不仅拖慢速度,还会放大 GC 压力,让基准结果失真。
立即学习“go语言免费学习笔记(深入)”;
- 用
go build -gcflags="-m -l" your_file.go查看逃逸分析报告;重点找... escapes to heap - 小切片优先用数组字面量(如
[4]int{1,2,3,4})而非make([]int, 4),前者栈分配 - 避免在
Benchmark循环里构造结构体指针(如&MyStruct{...}),改用栈上变量+取地址 - 如果必须分配,用
b.SetBytes(int64(len(data)))告诉测试框架数据规模,让ns/op能换算成ns/byte,便于横向比较
基准测试最麻烦的从来不是写代码,而是让每次运行都落在同一片“安静”的系统上下文里。参数、环境、逃逸——三者只要一个没控住,数据就只是看起来像那么回事。











