ycgg的GO语言之路Day04——性能调优实践| 青训营笔记

50 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

Day04——性能调优实战

性能调优的一些原则:

  • 要根据实际开发环境和具体的数据分析,而不是主观的猜测
  • 要专注定位对性能影响较大的瓶颈而不是细枝末节
  • 最好不要过早优化,防止后续升级代码导致无法正常运行造成限制
  • 不要过度优化

性能分析工具pprof优化

可以知道应用具体对CPU和内存的消耗,有可视化工具

功能简介图:

image-20230119144158185.png

运行提前准备好的项目

浏览器输入,访问debug的网页

localhost:6060/debug/pprof/

image-20230119144824870.png

网页中有一些指标和选项

CPU

可以在终端输入采样命令,此处采样10s

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"

运行完成后,终端输入top命令,可以查看详细信息

image-20230119145326890.png

flat 当前函数本身的执行耗时

flat% flat占 CPU总时间的比例

sum%上面每一行的flat%总和

cum指当前函数本身加上其调用函数的总耗时

cum%cum占CPU 总时间的比例

可以根据占比和时间长度找到限制程序效率的关键函数

终端输入list 【函数名】

可以查看具体函数中占用时间最长的代码,如下图,定位到for循环中的一步

image-20230119145645325.png

使用web命令,可以自动打开网页有可视化调用关系和占比

image-20230119145816163.png

Heap——堆内存

使用可视化命令

go tool pprof -http=:8080"http://localhost:6060/debug/pprof/heap"

打开后可以看到有VIEW选项

image-20230119150133659.png

具体选项和刚刚在终端使用 的命令类似,比如top

Top视图

类似于top命令

image-20230119150255730.png

Source视图

类似于终端list命令的结果

image-20230119150329119.png

点开网页上的SAMPLE,可以看到四个选项

  • alloc_objects:程序累计申请的对象数
  • inuse objects:程序当前持有的对象数
  • alloc_space:程序累计申请的内存大小
  • inuse space:程序当前占用的内存大小

打开alloc_space,可以看到,有代码申请了大量额外的内存,这也可以对代码效率造成影响,而通过刚刚的排查,很可能会忽略掉

image-20230119150700286.png

goroutine协程

使用时可以仿照刚刚的命令,只需要修改最后一个后缀即可

go tool pprof -http=:8080"http://localhost:6060/debug/pprof/goroutine"

打开后发现调用图非常长,不易分析

这次尝试使用flame graph火焰图去分析

从上到下表示调用顺序,每一块代表一个函数,块越长代表CPU占用时间越长,并且火焰图是动态的,可以点击对应函数块进行分析

image-20230119151053460.png

mutex锁

命令类似前几步:

go tool pprof -http=:8080"http://localhost:6060/debug/pprof/mutex"

查看的步骤也和刚刚类似,看哪种视图方便就用哪种

block阻塞

 go tool pprof -http=:8080"http://localhost:6060/debug/pprof/block"

操作类似

打开后发现先前最开始可以看到有2个阻塞,但是只显示了一个

原因就是,pprof会自动过滤一些时间极短的,防止展示内容过多影响分析,如果想看可以去首页点击对应的block链接查看

image-20230119151606172.png

小结

标星处为需要调优时重点关注的地方

image-20230119151815831.png

pprof采样过程和原理

对于CPU来说,采样对象:函数调用和它们占用的时间,采样率:100次/秒,固定值,采样时间:从手动启动到手动结束

流程图如下:

image-20230119152101995.png

操作系统:每10ms向进程发送一次SIGPROF信号

进程:每次接收到SIGPROF会记录调用堆栈

写缓冲:每100ms读取已经记录的调用栈并写入输出流

对于Heap堆内存来说,采样程序通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量

采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录

采样时间:从程序运行开始到采样时

采样指标: alloc_space, alloc_objects, inuse_space, inuse_objects计算方式: inuse = alloc - freo

对于协程来说

Goroutine:记录所有用户发起且在运行中的 goroutine(即入口非runtime开头的)runtime.main的调用栈信息

ThreadCreate:记录程序创建的所有系统线程的信息

image-20230119152601345.png

对于阻塞来说,采样阻塞操作的次数和耗时,采样率:阻塞耗时超过阈值的才会被记录,1为每次阻塞均记录

对于锁来说,采样争抢锁的次数和耗时,采样争抢锁的次数和耗时,采样率:只记录固定比例的锁操作,1为每次加锁均记录

性能调优案例

1.业务服务优化

基本概念:

服务:能单独部署,承载一定功能的程序

依赖:Service A 的功能实现依赖

Service B的响应结果,称为Service A依赖 Service B

调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系

基础库:公共的工具包、中间件

优化流程:

建立服务性能评估手段-->分析性能数据,定位性能瓶颈-->重点优化项改造-->优化效果验证

建立服务性能评估手段

服务性能评估方式:单独benchmark无法满足复杂逻辑分析,不同负载情况下性能表现差异

请求流量构造:不同请求参数覆盖逻辑不同.线上真实流量情况

压测范围:单相器压测,集群压测(通常要借助于一些压测平台的压测报告去模拟)

性能数据采集:单机性能数据,集群性能数据

分析性能数据,定位性能瓶颈

使用库如果不规范,高并发场景高峰期使用优化会造成性能下降

重点优化项改造

必须优先保证程序的正确性,如果优化后改变不大,可能并不是关键影响程序的位置

优化效果验证

重复进行压测并且上线实际评估优化的效果,对比优化前数据看有没有根本性改变

进一步优化服务整体链路

比如规范接口,明确场景需求,是不是更小的数据集就可以满足需求,尽可能简洁调用,避免多次无意义多次调用

2.基础库优化

优化步骤也类似上面

1)分析基础库核心逻辑和性能瓶颈

设计完善改造方案

数据按需获取

数据序列化协议优化

2)内部压测验证

3)推广业务服务落地验证

3.GO语言本身的优化

优化内存分配

优化代码编译流程,生成更高效的程序

内部压测验证

推广业务服务落地验证