要真正测出性能差异,需确保基准测试中b.N被正确使用,避免手动循环导致结果失真;go test -bench会动态调整b.N直至统计稳定。

怎么用 go test -bench 真正测出性能差异
基准测试不是跑一次就完事,b.N 是动态调整的,系统会自动拉长循环次数,直到统计足够稳定。但如果你在 for i := 0; i 里做了初始化(比如 <code>make([]int, 0)),这部分开销会被算进结果,导致误判。
- 初始化逻辑必须写在
b.ResetTimer()之前——这是硬性要求,否则测的是“创建+执行”,不是“纯执行” - 避免在循环里 new 对象、打开文件、调用网络;这些不是你要比的点,是噪声
- 加
-benchmem一起跑,看B/op和allocs/op,内存分配次数比耗时更早暴露问题 - 对比两个实现时,函数名要不同(如
BenchmarkAppendWithCapvsBenchmarkAppendWithoutCap),否则go test会跳过后者
切片预分配到底该设多少容量
预分配不是“越大越好”,也不是“猜一个数”。关键是让 append 在整个生命周期内不触发扩容——因为每次扩容都要 malloc 新底层数组、copy 旧数据、释放旧内存,三步全堆上操作。
- 如果已知最终长度(比如读取固定大小的 JSON 数组),直接用
make([]T, 0, expectedLen) - 如果长度有波动但可估上限(如日志行数 ≤ 1000),按上限预分配,宁可多占点栈外内存,也别让 GC 频繁扫小对象
- 千万别写
make([]T, len(src))然后append——这等于先填满再扩,白干一次 copy - 注意:预分配后仍要用
append,不是直接下标赋值;否则越界 panic,且长度不会自动更新
为什么 strconv.Itoa 比 fmt.Sprintf 快,但不能乱换
strconv.Itoa 是纯整数转字符串的专用函数,无格式解析、无反射、无接口转换;fmt.Sprintf("%d", n) 要走格式字符串解析 + 类型检查 + 接口装箱,慢一倍以上是常态。但它的适用边界很窄。
- 只适用于
int/int64等整型 → 字符串场景;换成浮点或带前导零就得换回fmt - 别为了省几纳秒,在热路径里把
string拼接硬拆成strconv++,Go 的字符串拼接在编译器层面已有优化 - 如果后续还要做
bytes.Buffer.WriteString,不如直接buf.WriteString(strconv.Itoa(n)),别中间多一次字符串分配
pprof CPU profile 怎么抓才不漏掉热点
本地起 net/http/pprof 看火焰图很香,但线上或 CLI 工具类程序没法开 HTTP server。这时候得靠手动采集,而很多人漏掉关键一步:采集时间太短,样本不够。
立即学习“go语言免费学习笔记(深入)”;
- 用
runtime/pprof.StartCPUProfile后,必须保证业务逻辑运行足够久(建议 ≥ 3 秒),否则生成的.prof文件几乎全是 runtime 系统调用,看不到你自己的函数 - 别在
main结束前立刻StopCPUProfile,加个time.Sleep(500 * time.Millisecond)让 goroutine 调度缓冲区刷干净 - 分析时用
go tool pprof cpu.prof进交互模式,输top看耗时 Top 函数,输web生成火焰图——别只盯着 flat%,要看 cum% 和调用链深度 - 如果发现热点在
runtime.mallocgc,说明不是算法慢,是对象分配太猛,该查go test -benchmem或内存 profile 了
真实项目里最常被忽略的,是把“优化”和“测量”割裂开:改完代码不跑 benchmark,凭感觉加 goroutine,或者看到 pprof 里某个函数红就去重写它——其实那只是被高频调用的入口,真正该优化的是它的上游输入或下游缓存策略。



















