这是我参与「第五届青训营」伴学笔记创作活动的第 4 天
1. 概述
我将主要介绍如下知识点:
- Go 语言性能分析工具 pprof
- pprof 排查实战
2. 性能分析工具 pprof
2.1 性能调优原则
在介绍实际优化手段之前,我们先来了解下性能调优原则,指导我们更合理的进行性能优化:
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
2.2 pprof 功能简介
pprof 是用于可视化和分析性能分析数据的工具
3. pprof 排查实战
3.1 搭建 pprof 实践项目
- 可先克隆代码到本地,随后编译运行(会占用1CPU核心和超过1GB的内存)
- 项目链接:github.com/wolfogre/go…
- 项目提前埋入了一些炸弹代码,产生可观测的性能问题
3.2 浏览器查看指标
-
在浏览器中打开
http://localhost:6060/debug/pprof -
可以看到如下页面: 这就是我们刚刚引入的net/http/pprof 注入的入口了
页面上展示了可用的程序运行采样数据且有简单说明,分别是:
allocs:内存分配情况
blocks:阻塞操作情况
cmdline:程序启动命令
goroutine:当前所有goroutine的堆栈信息
heap:堆上内存使用情况(同alloc)
mutex:锁竞争操作情况
profile:CPU占用情况
threadcreate:当前所有创建的系统线程的堆栈信息 trace:程序运行跟踪信息
-
点击 allocs 进去,看到如下页面,可以看出数据可读性很差,故我们一会儿借助 pprof 工具帮助阅读这些指标
-
由于是在 CPU,堆内存,goroutine,锁竞争和阻塞操作上埋了炸弹,所有可以使用 pprof 工具分析上述
因为 cmdline 显示运行进程的命令,threadcreate 比较复杂,不透明,trace需要另外的工具解析,故这些暂不涉及
3.3 CPU
我们先从CPU问题排查开始,
-
不同的操作系统工具可能不同,可以首先使用自己熟悉的工具看看程序进程的资源占用发现问题
-
pprof 的采样结果是将一段时间内的信息汇总输出到文件中,所以首先需要提前拿到这个 profile 文件
-
你可以直接使用暴露的接口链接下载文件后使用,也可以直接用 pprof 工具连接这个接口下载需要的数据,这里我们使用
go tool pprof + 采样链接来启动采样,即go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"链接中就是刚刚「炸弹」程序暴露出来的接口,链接结尾的profile 代表采样的对象是CPU使用。如果你在浏览器里直接打开这个链接,会启动一个60秒的采样,并在结束后下载文件。这里我们加上seconds=10的参数,让它采样十秒。 稍等片刻,我们需要的采样数据已经记录和下载完成,并展示出pprof 终端
-
输入
top命令,可以查看CPU占用最高的函数,默认会展示资源占用最高的10个函数,如果只需要查看最高的N个函数(例如查看最高的3个调用,输入 top3)可以看到如下结果:
这五列从左到右分别是:
可以看到表格的第一行里,
Tiger.Eat 函数本身占用3.12秒的CPU时间,占总时间的98.11%,显然问题就是这里引起的 -
接着,输入
list Eat查找这个函数,看看具体是哪里出了问题(list 命令会根据后面给定的正则表达式查找代码),并按行展示出每一行的占用,可以看到,第24行有一个100亿次的空循环,占用了3.56秒的CPU时间,问题就在这儿了,定位成功
3.4 Heap - 堆内存
注释 CPU 问题代码,重新运行后,打开活动监视器,可以发现进程的CPU已经降下来了,然而内存使用还是很高;接着我们来排查内存问题,
- 同样的,在终端输入如下命令:
go tool pprof "http://localhost:6060/debug/pprof/heap"
-
然后输入
top,可以明显看到,这里出问题的 *Mouse.Steal() 函数,它占用了 1.2GB 内存 -
最后输入
list Steal,如下页面,自行找到问题代码并注释即可
3.5 goroutine - 协程
Golang 是一门自带垃圾回收的语言,一般情况下内存泄露是没那么容易发生的,但是有一种例外:goroutine是很容易泄露的,进而会导致内存泄露,所以接下来,我们来看看 goroutine 的使用情况,
-
在终端输入如下命令:
go tool pprof "http://localhost:6060/debug/pprof/goroutine" -
发现「炸弹」程序已经有一百多条 goroutine 在运行了,这个量级并不是很大,但对于一个简单的小程序来说还是很不正常的
-
可以看到,(*Wolf).Drink() 这个调用创建了超过90%的goroutine,输入
list Drink即可定位问题代码,找到并注释即可
4. 总结
剩下 锁 和 阻塞 的问题相对来说实际工作不常遇到,这里就不再讲述,感兴趣的同学可自行上网查阅。
另外,其实有两种展示方式:可视化终端和网页,四种视图:Top、 Graph、 源码和火焰图,本篇文章只展示了可视化终端 和 Top,但是网页端的展示方式会更直观(大家后续可重点查阅下),笔者也还在学习的过程中....
参考:
- 字节内部课:GO性能优化分析方式