基准测试需多次运行取稳定均值;必加-benchmem和-count=5,固定gomaxprocs,b.resettimer()置于初始化后循环前,避免i/o、逃逸和重复分配。

用 go test -bench 跑出可比的数字
基准测试不是跑一次看耗时,而是让 testing.B 自动多次执行并取稳定均值。不加 -benchmem 会漏掉内存分配关键指标;不加 -count=5 单次结果波动大,容易误判优化效果。
-
go test -bench=^BenchmarkFoo$ -benchmem -count=5 -cpu=1,2,4:强制固定 GOMAXPROCS,排除调度干扰 - 函数名必须以
Benchmark开头,参数必须是*testing.B -
b.ResetTimer()放在初始化代码之后、循环之前,避免把 setup 时间算进耗时 - 别在循环里用
fmt.Println或log——I/O 会严重污染结果
写 Benchmark 函数时怎么避免常见陷阱
最常踩的坑是变量逃逸或重复初始化:比如每次循环都 make([]int, 1000),实际测的是分配速度而非算法逻辑;又或者闭包捕获了外部变量,导致编译器无法内联。
- 提前分配好数据结构,循环内只操作(例如
data := make([]int, n); b.ResetTimer(); for i := 0; i ) - 用
go tool compile -S检查关键函数是否被内联,没内联可能意味着接口/反射/闭包干扰 - 避免在
Benchmark中调用time.Now()、rand.Intn()等非确定性操作 - 如果对比两个实现,确保它们处理完全相同的数据(建议用固定 seed 的
rand.New初始化)
如何判断 ns/op 差异是否真有意义
差 5% 不一定代表性能提升,尤其当标准差(std dev)超过均值 3% 时,大概率是噪声。Go 的 benchstat 工具能帮你做统计显著性判断,比肉眼盯数字靠谱得多。
- 先分别保存两组结果:
go test -bench=. -count=10 > old.txt和> new.txt - 运行
benchstat old.txt new.txt,关注p-value是否 0.05,以及geomean变化率 - 如果
allocs/op下降但ns/op上升,要结合业务场景权衡——GC 压力小有时比纯快更重要 - 注意 CPU 频率是否被降频(
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq),笔记本上插电和电池模式结果可能差 20%
对比不同 Go 版本或 GC 参数时要注意什么
升级 Go 版本后 ns/op 变差,未必是回归,可能是默认 GC 阈值或调度器行为变了。直接改 GOGC 或 GOMAXPROCS 会影响所有 benchmark,必须显式控制。
- 用
os.Setenv("GOGC", "off")在init()中关闭 GC,测纯计算路径(记得恢复) - 跨版本对比务必用相同硬件、相同内核、相同
GOOS/GOARCH,且禁用 ASLR:setarch $(uname -m) -R ./test - Go 1.21+ 默认启用
goroutine stack traces采样,对高频小函数 benchmark 有轻微影响,可用GODEBUG=gctrace=0关闭 - 不要只信
go version输出,用runtime.Version()确认实际链接的运行时版本
真实服务里,CPU cache line 对齐、false sharing、NUMA 节点绑定这些因素,比单个函数的 ns/op 更影响最终吞吐——benchmark 只是起点,不是终点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











