ns/op低不等于快,因测试内容差异大;B/op和allocs/op才是反映GC压力的关键指标,需加-benchmem才能显示;MB/s需手动调用b.SetBytes()设置数据量才生效。

ns/op 数值低不等于快,得看它测的是什么
你看到 BenchmarkSum-8 200 ns/op,BenchmarkHTTPHandler-8 85000 ns/op,别急着说后者慢十倍。前者可能只是两个整数相加,后者包含 TLS 握手、HTTP 解析、JSON 序列化、goroutine 调度——它测的是一整条链路,不是单个函数。
常见误判点:
- 没调用
b.ResetTimer(),把初始化(比如make([]byte, 1e6)或读配置)算进了ns/op,结果虚高 - 被测函数返回值没被使用,编译器直接优化成空循环;必须用全局变量兜住,比如
var blackhole interface{}; blackhole = f() - 输入规模固定(如
fib(30)),和真实业务中动态长度的数据完全不匹配
B/op 和 allocs/op 才是线上服务的“血压计”
ns/op 不会喊疼,B/op 和 allocs/op 才会。GC 压力不会体现在延迟数字里,却会让 P99 突然飙升、STW 拉长。
必须加 -benchmem 才能看到这两项——默认不输出,不是没有分配,是没统计。
立即学习“go语言免费学习笔记(深入)”;
典型问题场景:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 循环里写
s += "x":每次拼接都 new 底层数组,B/op随字符串长度线性涨 - 写
&MyStruct{}:强制堆分配,allocs/op立刻 +1 - 反复
make([]int, n):应改用make([]int, 0, n)预分配 - 字符串构造用
string([]byte{...}):底层数据逃逸到堆,B/op= 实际字节数(如 "ABC" → 3 B/op)
MB/s 不是自动出来的,不设 b.SetBytes() 就没有
MB/s 只在 IO 密集型场景有意义:JSON 编解码、文件读写、网络包处理。但它不会自动出现——你必须显式告诉测试框架“这次操作处理了多少字节”。
正确做法:
- 在
b.ResetTimer()之前调用b.SetBytes(int64(len(data))) - 数据是 1MB JSON,就传
1024 * 1024;传1024会导致 MB/s 差 1000 倍 - 如果每次处理数据量不稳定(比如随机长度 slice),
MB/s失去横向比较价值
漏掉这行,输出里就只有 ns/op 和 B/op,压根不会显示 MB/s。
-8 不是用了 8 个物理核心,而是 GOMAXPROCS=8
后缀里的 -8 表示当前 GOMAXPROCS 值,即 Go 运行时允许并行的 OS 线程上限。它不等于逻辑核数,也不代表实际并发度。
想真正对比单核 vs 多核表现,必须显式指定:
-
go test -bench=. -cpu=1,4,8 -benchmem→ 输出BenchmarkXxx-1、-4、-8三行 - 若被测函数本身无并发逻辑(如纯计算),提高
-cpu可能因调度开销反拉高ns/op - 对
sync.Map这类并发安全结构,-cpu才真正影响行为
最易忽略的一点:allocs/op 稳定大于 0(比如恒为 3),说明每次调用都固定逃逸分配,不是偶然现象——这时该立刻跑 go tool compile -gcflags="-m" *.go 看逃逸分析,而不是盯着 ns/op 反复调参。

















