go pprof
- 一、性能调优原则
- 二、pprof
- 1、pprof 功能简介
- 2、pprof 排查实战
- 前置工作
- a、CPU
- b、Heap
- c、goroutine
- d、mutex
- e、block
- 3、pprof 的采样过程和原理
- a、cpu
- b、heap
- c、goroutine && threadCreate
- d、block && mutex
- 三、调优流程
- 1、业务优化
- a、流程
- 2、基础库优化
- 3、go语言优化
- 总结
- 参考资料
go pprof简介
一、性能调优原则
- 依靠数据而不是猜测,分析cpu/memory/网络/磁盘io/goroutine/mutex等具体数据情况,来分析问题。
- 重点先行,根据优先级,应该先优化最大的性能瓶颈处,而不是细枝末节。
- 不要过早优化,毕竟现在项目开发模式为–小步快走+反复迭代,API容易过时。
- 不要过度优化,同样是项目小步快走+反复迭代,该API可能过时,也可能大改,过度优化会增加系统复杂度,且不一定后期能用。
二、pprof
性能调优依靠数据而不是猜测,我们希望应用在什么地方消耗多少cpu/memory等,才能使我们的分析问题更加快速和准确。
go自带的pprof工具不仅可以知道应用在什么地方消耗多少cpu/memory等,而且还能可视化,提高分析速度。
1、pprof 功能简介
2、pprof 排查实战
前置工作
- 搭建pprof实践项目,Wolfogre,该项目针对cpu/memory等埋了很多累,可以让性能问题更加明显。
- 需要1CPU核心+1G内存。
a、CPU
- 采样数据,
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
,
1.top,查看函数占用资源的情况。
指标 | 含义 |
---|---|
flat | 当前函数本身的执行耗时 |
flat% | flat占cpu总时间的比例 |
sum% | 上面每一行flat%的累计和 |
cum | flat+调用其它函数的耗时 |
cum% | cum占cpu总时间的比例 |
可以发现,
Eat耗时最严重,所以重点分析Eat源代码;
当flat == 0时,说明函数中只有调用其它函数的代码;
当flat == cum时,说明该函数没有调用其它函数。
2.list,查找指定函数代码行耗时。
可以发现,
耗时代码为一个for循环。
3.web,调用关系可视化。
b、Heap
- 采样数据,
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
,以web的形式展示,而不是终端。 - top图
- source图
- sample
指标 | 含义 |
---|---|
alloc_objects | 累计申请对象数 |
alloc_space | 累计申请内存大小 |
inuse_objects | 当前持有对象数 |
inuse_space | 当前占用内存大小 |
c、goroutine
- goroutine泄露会导致内存泄露。
- 采样数据,
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
。
d、mutex
- 采样,
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
。
e、block
- 采样,
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
。
3、pprof 的采样过程和原理
a、cpu
指标 | 内容 |
---|---|
采样对象 | 函数调用占用时间 |
采样时间 | 手动启动到手动结束 |
采样率 | 100次/秒,固定值 |
采样简要流程,开始采样 > 设定信号处理函数 > 开启定时器 > 采样 > 停止采样 > 取消信号处理函数 > 关闭定时器 > 结束采样。
层 | doing |
---|---|
os | 每10s向进程发送一次sigprof信号 |
进程 | 每接收到sigprof信号就记录调用堆栈 |
写缓冲 | 每100ms读取记录的调用栈写入输出流 |
b、heap
依赖内存分配器的记录数据,记录分配/释放的对象数量和内存大小。
指标 | 内容 |
---|---|
采样时间 | 从程序开始采样的到当前直至结束整个过程 |
采样指标 | alloc_space alloc_objects inuse_space inuse_objects |
采样率 | 每分配512kb记录一次,可在运行开头修改,1为每次分配均记录 |
计算方式 | inuse = alloc - free |
c、goroutine && threadCreate
- goroutine,记录用户发起且在运行中的goroutine。Stop the world > 遍历allg切片 > 输出创建g的堆栈 > start the world.
- threadCreate,记录程序创建的所有系统线程的信息。Stop the world > 遍历allm链表 > 输出创建m的堆栈 > start the world.
d、block && mutex
- block,采样阻塞操作的次数和耗时,阻塞耗时超过阈值的才会被记录,1为每次阻塞均记录。
- mutex,采样争抢锁的次数和耗时,只记录固定比例的锁操作,1为每次加锁均记录。
三、调优流程
对逻辑相对复杂的程序如何进行性能调优?优化哪些部分?调优流程如何?
- 业务服务优化,具体业务,如点赞评论。
- 基础库优化,如日志库。
- Go语言优化。
1、业务优化
基本概念 | 含义 |
---|---|
服务 | 能单独部署,承载一定功能的程序 |
依赖 | service A的功能实现依赖service B的响应结果,称service A 依赖service B |
调用链路 | 能支持一个接口请求的相关服务集合及相互之间的依赖关系 |
基础库 | 公共的工具包、中间件 |
a、流程
- 建立服务性能评估手段
角度 | 内容 |
---|---|
性能评估方式 | 单独的benchmark无法满足复杂的逻辑分析 不同负载情况下表现会有差异 |
请求流量构造 | 不同请求参数覆盖的逻辑不同 线上真实的流量情况 |
压测范围 | 单机器压测 集群压测 |
性能数据采集 | 单机性能数据 集群性能数据 |
- 分析数据,定位性能瓶颈
根据压测报告数据,结合火焰图,来定位性能瓶颈代码处。可能是使用库规范;高并发场景优化不足(无意义的同步操作); - 重点优化改造
以正确性为基础,录制优化前的响应,对比优化后的响应。 - 优化效果验证
优化完成后,再次压测,根据压测报告的情况,才能发布上线,并关注线上运行是否有响应的优化效果。关注服务监控 > 逐步放量 > 收集性能数据。
注:进一步优化,则是看一个接口服务的整条链路,分析并优化。
2、基础库优化
- 统计基础库使用占比
- 分析基础库核心逻辑和性能瓶颈
- 内部压测验证
- 推广业务服务落地验证
3、go语言优化
- 优化内存分配策略,如gc时的内存分配与回收,根据自己的业务运行统计数据来具体分析。
- 优化代码编译流程,生成更高效的程序,函数内联,逃逸分析等。
- 内部压测验证
- 推广业务服务落地验证
注:go语言优化,接入简单,只需用新sdk编译并运行程序,且通用性强。
总结
- 性能优化原则,依靠数据而不是猜测。
- pprof工具使用,针对cpu/memory等数据进行采集,基于数据去可靠分析。
- 根据pprof采集到的数据,利用top/source/graph等方式灵活分析问题。
- pprof采用过程和原理。
- 性能调优要保证正确性,且先定位最大性能你瓶颈。
参考资料
[1] pprof实战代码
[2] pprof实战