Go 高质量编程与性能调优 | 青训营笔记

100 阅读7分钟

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

一、性能分析工具pprof

pprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。

  • 是用于可视化和分析性能分析数据的工具。

  • profile.proto 读取分析样本的集合,并生成报告以可视化并帮助分析数据(支持文本和图形报告)。

  • profile.proto 是一个 Protocol Buffer v3 的描述文件,它描述了一组 callstack 和 symbolization 信息, 作用是表示统计分析的一组采样的调用栈,是很常见的 stacktrace 配置文件格式。

image.png

1.浏览器查看指标

  • allocs:内存分配情况
  • blocks:阻塞操作情况
  • cmdline:程序启动命令及
  • goroutine:当前所有goroutine的堆栈信息
  • heap:堆上内存使用情况(同alloc)
  • mutex:锁竞争操作情况
  • profile: CPU占用情况
  • threadcreate:当前所有创建的系统线程的堆栈信息
  • trace:程序运行跟踪信息

1.CPU

命令:topN

查看占用资源最多的函数

  • Flat:当前函数的占用
  • Flat%: Flat占总量的比例
  • Sum%:上面所有行的Flat%总和
  • Cum (Cumulative):当前函数加上其调用函数的总占用
  • Cum%: Cum占总量的比例

Cum-Flat得到的是函数中调用其他函数所消耗的资源,所以在函数中没有对其他函数进行调用时,Cum-Flat=O,也就是Flat=cum。相应地,函数中除了调用另外的函数,没有其他逻辑时,Flat=O。

当函数中没有调用其他函数时flat=cum,函数中只有调用其他函数的逻辑时flat=O


命令:list

根据指定的正则表达式查找代码行,并按行展示出每一行的占用。


命令:web

调用关系可视化

image.png


2.堆内存

可以通过-http=:8080参数,可以开启pprof自带的Web Ul,性能指标会以网页的形式呈现。

切换回Source视图,可以查看调用和具体的源码视图。

sample的四个指标: image.png


3.协程

打开View菜单,切换到Flame Graph视图可以看到,刚才的节点被堆叠了起来。图中,自顶向下展示了各个调用,表示各个函数调用之间的层级关系每一行中,条形越长代表消耗的资源占比越多。显然,那些“又平又长”的节点是占用资源多的节点。


4.锁、阻塞

修改链接后缀,分别改成mutex和block,然后打开网页观察,即可发现对应的结果。


二、pprof采样过程和采样原理

1.CPU

CPU采样会记录所有的调用栈和它们的占用时间。

在采样时,进程会每秒暂停一百次,每次会记录当前的调用栈信息。汇总之后,根据调用栈在采样中出现的次数来推断函数的运行时间。你需要手动地启动和停止采样。每秒100次的暂停频率也不能更改。

这个定时暂停机制在unix或类unix系统上是依赖信号机制实现的。

每次“暂停”都会接收到一个信号,通过系统计时器来保证这个信号是固定频率发送的。

image.png

一共有三个相关角色:进程本身、操作系统和写缓冲。

启动采样时,进程向OS注册一个定时器,OS会每隔10ms向进程发送一个SIGPROF信号,进程接收到信号后就会对当前的调用栈进行记录。

与此同时,进程会启动一个写缓冲的goroutine,它会每隔100ms从进程中读取已经记录的堆栈信息,并写入到输出流。

当采样停止时,进程向OS取消定时器,不再接收信号,写缓冲读取不到新的堆栈时,结束输出。

image.png


2.堆内存

内存采样在实现上依赖了内存分配器的记录,所以它只能记录在堆上分配,且会参与Gc的内存,一些其他的内存分配。例如调用结束就会回收的栈内存、一些更底层使用cgo调用分配的内存,是不会被内存采样记录的。

它的采样率是一个大小,默认每分配512KB内存会采样一次,采样率是可以在运行开头调整的,设为1则为每次分配都会记录。

与CPU和goroutine都不同的是,内存的采祥是一个持续的过程,它会记录从程序运行起的所有分配或释放的内存大小和对象数量,并在采样时遍历这些结果进行汇总。


3.协程和线程创建

Goroutie采样会记录所有用户发起,也就是入口不是runtime开头的goroutine,以及main函数所在goroutine的信息和创建这些goroutine的调用栈。

他们在实现上丰常的相似,都是会在STW之后,遍历所有goroutine/所有线程的列表并输出堆栈,最后Start The World继续运行。这个采祥是立刻触发的全量记录,你可以 人通过比较两个时间点的差值来得到某一时间段的指标。

image.png


4.阻塞、锁

这两个采样记录的都是对应操作发生的调用栈、次数和耗时,不过这两个指标的采样率含义并不相同。

阻塞操作的采样率是一个“阈值”,消耗超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。锁竞争的采样率是一个“比例”,运行时会通过随机数来只记录固定比例的锁操作,1为每次操作都会记录。

它们在实现上也是基本相同的。都是一个“主动上报”的过程。

在阻塞操作或锁操作发生时,会计算出消耗的时间,连同调用栈一起主动上报给采样器,采样器会根据采样率可能会丢弃一些记录。

在采样时,采样器会遍历已经记录的信息,,统计出具体操作的次数、调用栈和总耗时。和堆内存一样,你可以对比两个时间点的差值计算出这段时间内的操作指标。

image.png


三、性能调优案例

1.基本概念

  • 服务:能单独部署,承载一定功能的程序。
  • 依赖:Service A的功能实现依赖Service B的响应结果,称为Service A依赖Service B。
  • 调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系。
  • 基础库:公共的工具包、中间件。

2.流程

1.业务服务优化

  1. 建立服务性能评估手段
    1. 服务性能评估方式
    • 单独benchmark无法满足复杂逻辑分析
    • 不同载荷情况下性能表现差异
    1. 请求流量构造
    • 不同请求参数覆盖逻辑不同
    • 线上真实流量情况
    1. 压测范围
    • 单机器压测
    • 集群压测
    1. 性能数据采集
    • 单机性能数据
    • 集群性能数据
  2. 分析性能数据,定位性能瓶颈
    • 使用库不规范
    • 高并发场景优化不足
  3. 重点优化项改造
    1. 正确性是基础
    2. 响应数据diff
    • 线上请求数据录制回放
    • 新旧逻辑接口数据diff
  4. 优化效果验证
    1. 重复压测验证
    2. 上线评估优化效果
    • 关注服务监控
    • 逐步放量
    • 收集性能数据
  5. 进一步优化,服务整体链路分析
    • 规范上流服务调用接口,明确场景需求
    • 分析链路,通过业务流程优化提升服务性能

2.基础库优化

  1. 分析基础库核心逻辑和性能瓶颈
    • 设计完善改造方案
    • 数据按需获取
    • 数据序列化协议优化
  2. 内部压测验证
  3. 推广业务服务落地验证

3.Go语言优化

  • 优化内存分配策略
  • ·优化代码编译流程,生成更高效的程序内部压测验证
  • 内部压测验证
  • 推广业务服务落地验证