性能优化
Go 的性能优化有成熟的工程路径:先测量、再定位、后优化。凭直觉优化不仅常常无效,还会牺牲可读性。本页给出方法论与最常见的高收益手段。
方法论:测量驱动
Tip
纪律:任何优化前后都要有同一份基准对比数字(benchstat 工具可判定差异是否显著);没有 10% 以上的可测量收益,不值得付出可读性代价。
内存分配:最大的性能杠杆
Go 程序的多数性能问题不是 CPU 计算慢,而是堆分配过多——分配与 GC 压力直接吃掉吞吐。
逃逸分析
编译器决定变量分配在栈还是堆。栈分配近乎免费(随函数返回回收),堆分配则触发 GC。用 -gcflags='-m' 观察逃逸:
常见逃逸触发点:返回局部变量指针、赋值给 interface{}(如 fmt.Println 参数)、闭包捕获、过大的栈对象。不必消除所有逃逸——重点看热路径上的高频逃逸。
字符串拼接
切片与 map 预分配
已知规模时给容量,避免多次扩容拷贝:
sync.Pool:复用临时对象
高并发路径上的临时缓冲可用对象池,直接降低 GC 压力:
Warning
sync.Pool 只适合"用完即弃"的临时对象;池中对象随时可能被 GC 回收,不能存放有状态资源(如数据库连接——那是连接池的职责)。
锁与并发
- 锁粒度:临界区只包住共享数据的读写,I/O 与计算放在锁外(见sync 包)。
- 读多写少:
RWMutex或不可变数据 + 原子指针替换(atomic.Pointer),读路径零锁。 - 无共享优先:每个 worker 独立累加、最后合并,往往比一把全局锁更快(参考工作池的分片思路)。
- 单一数值计数用
atomic.Int64,比互斥锁低一个数量级的开销。
GC 调优
绝大多数服务不需要调 GC,减少分配才是根本。确实需要时,了解两个环境变量:
数据库与网络 I/O
I/O 密集服务的性能瓶颈通常在连接管理,而不是语言层:
其他高收益项:
- 消灭 N+1 查询:列表页循环查库改为一次
IN查询或 JOIN。 - 批量写入:逐条 INSERT 合并为批量(GORM
CreateInBatches)。 - 只查需要的列:大宽表避免
SELECT *。 - HTTP 服务保持默认 keep-alive;出站调用确认连接复用(共享 http.Client)。
性能反模式速查
Tip
优先级建议:I/O 效率(批量、连接池、缓存)> 分配 reduction > 锁优化 > 微观指令级优化。前两类覆盖 90% 的收益;最后一类仅在基准证明热点后进行,且通常轮不到 Go 层去做。
小结
- 一切优化从基准与 pprof 出发,用数字证明收益。
- 分配是 Go 性能的第一杠杆:预分配、Builder、sync.Pool、关注热路径逃逸。
- 并发优化优先"减少共享",其次才是更细的锁。
- 容器设置
GOMEMLIMIT,连接池三参数是 I/O 服务的必修项。