推荐使用 go test -bench 进行基准测试,需将待测函数置于 xxx_test.go 中,以 BenchmarkXxx 命名,并通过 b.Run() 分别注册两个实现进行对比。

Go 的 go test -bench 是唯一推荐的基准对比方式,手动写 time.Now() 测量不仅不准,还容易因编译器优化、GC 干扰、CPU 频率波动导致结果失真。
怎么用 go test -bench 对比两个函数性能
把待测函数放在 xxx_test.go 文件里,用 BenchmarkXxx 命名,调用 b.Run() 分别注册两个实现:
func BenchmarkAddV1(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = addV1(1, 2)
}
}
func BenchmarkAddV2(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = addV2(1, 2)
}
}
func BenchmarkCompare(b *testing.B) {
b.Run("v1", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = addV1(1, 2)
}
})
b.Run("v2", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = addV2(1, 2)
}
})
}
运行命令:go test -bench=^BenchmarkCompare$ -benchmem -count=5
-
-bench=^BenchmarkCompare$精确匹配,避免误跑其他 benchmark -
-benchmem输出内存分配统计(B/op和allocs/op),这对判断是否逃逸很关键 -
-count=5重复 5 次取中位数,降低单次噪声影响
为什么不能直接比较两次 go test -bench 的单独输出
单独跑 BenchmarkAddV1 和 BenchmarkAddV2 会各自触发 GC、受 CPU 调度抖动、甚至被系统进程抢占,两次结果不具备可比性。
立即学习“go语言免费学习笔记(深入)”;
- Go 的 benchmark 运行时会自动调整
b.N让单轮耗时稳定在 ~100ms–1s,不同函数的b.N可能差几倍,直接看ns/op数值没意义 - 必须用
b.Run()在同一 benchmark 生命周期内执行,共享相同的 GC 周期、预热状态和调度上下文 - 如果函数有副作用(比如修改全局 map),还要注意加
b.ResetTimer()或b.StopTimer()控制计时区间
-benchmem 显示的 B/op 和 allocs/op 怎么看
这两个数字比 ns/op 更反映真实开销,尤其在高频调用场景下:
-
B/op表示每次操作平均分配多少字节内存;0 B/op说明完全栈上分配,无堆压力 -
allocs/op是每次操作触发几次堆分配;大于 0 就意味着 GC 会被唤醒,长期运行可能拖慢整体吞吐 - 例如切片拼接:
append(s, x...)如果底层数组不够,会触发一次扩容分配,表现为16 B/op+1 allocs/op;而预分配好容量后就变成0 B/op
真正难的是让 benchmark 贴近真实使用路径:输入数据要够大、要避免被编译器常量折叠、循环体里不能漏掉对结果的使用(否则整段代码可能被优化掉)。这些细节不处理,跑出来的数字再漂亮也没参考价值。



















