Go性能调优实战
1. 性能调优建议
1.1 简介
- 性能优化的前提是满足正式可靠、简洁清晰等质量因素
- 性能优化是综合评估,有时候时间效率和空间效率可能对立
- 避免常见性能陷阱可以保证大部分程序性能
- 越高级的性能优化手段越容易出现问题
1.2 工具
Go的基准性能测试工具:
Benchmark
1.3 Slice、Map预分配内存
- 切片底层是一个数组片段,有固定的内存分配,Map同理
- 如果超过固定内存,底层会进行扩容
- 切片操纵并不复制切片指向的元素
- 提前分配好空间可以减少拷贝和Rehash的消耗
1.4 字符串处理
- 字符串拼接时,使用
'+'性能最差,strings.Builder和bytes.Buffer更好,前者更快- 字符串是不可变类型、占用内存固定,拼接时会重新分配内存,而
strings.Builder和bytes.Buffer底层都是[]byte数组,不需要重新分配内存
1.5 空结构体
- 空结构体
struct{}实例不占据内存空间- 可作为占位符使用,更节省资源
1.6 atomic包
- 对于并发编程来说,atomic性能比加锁高很多,通过操作硬件实现
atomic通常用于保护变量,而sync.Mutex通常用于保护一段逻辑- 实际用例(int变量加1):
atomic.AddInt32(&c.i, 1)
2. 性能分析工具pprof实战
2.1 性能调优原则
- 依靠数据而不是猜测
- 定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
2.2 pprof功能简介
用于性能数据分析,定位耗费CPU、memory运行指标的文件或函数
- 分析-
Profile- 展示-
View- 工具-
Tool- 采样-
Sample
2.3 pprof排查实战
- web可视化分析地址:http://localhost:6060/debug/pprof
- 命令行分析:
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
2.4 pprof的采样过程和原理
- CPU 采样:采样函数调用和它们占用的时间
- 堆内存采样:通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
- 协程和系统线程采样:记录所有goroutine
runtime.main的调用栈信息、程序创建的所有系统线程的信息- 阻塞操作和锁竞争采样:采样阻塞操作的次数和耗时、争抢锁的次数和耗时
3. 性能调优案例
3.1 业务服务优化
- 服务:能单独部署、承载一定功能的程序
- 依赖:如Service A的功能实现依赖Service B的响应结果,则Service A依赖Service B
- 调用链路:支持一个接口请求的相关服务集合及其相互间的依赖关系
- 基础库:公共的工具包、中间件
流程:
graph TD
建立服务性能评估手段 --> 分析性能数据,定位性能瓶颈 --> 重点优化项改造 --> 优化效果验证
3.2 基础库优化
流程:
graph TD
分析基础库核心逻辑和性能瓶颈 --> 内部压测验证 --> 推广业务服务落地验证
3.3 Go语言优化
流程:
graph TD
优化内存分配策略 --> 优化代码编译流程,生成更高效的程序 --> 内部压测验证 --> 推广业务服务落地