pprof实战 | 青训营

pprof是Golang中一个强大的性能分析工具,可以用来分析CPU、内存的使用情况、阻塞情况、Goroutine的堆栈信息以及锁的争用情况等性能问题。Go语言内部就内置了pprof的采样工具,主要在runtime/pprofnet/http/pprof两个包中。pprof工具可通过网页或可视化终端进行分析,展示方式有Top图、调用图、火焰图、Peek、源码和反汇编等。

本文所展示实例来源于go-pprof-practice,一个故意设置"炸弹"来提供pprof锻炼的Go程序,它基本覆盖了常见的性能问题。(注意:为了运行该程序,请保证那你的机器至少有2核CPU和2G内存。)

运行程序,在浏览器中进入http://localhost:6060/debug/pprof/就可以观察到如下图所示:

从上到下依次是内存分配情况、阻塞操作情况、程序启动的命令及参数、所有Goroutine堆栈信息、堆上内存使用情况、互斥锁的争用情况、CPU占比情况、线程创建情况和程序运行的跟踪信息。每一个点开可以看到详细情况(其中profile和strace会下载文件而不是在浏览器打开),但不建议这么做,因为很多,很难去看到自己想要的东西。

直接阅读这些信息很不直观,我们可以通过go tool pprof命令来进一步得到更好的信息,这个命令go自带,无需安装。接下来我们直接开始正式分析。

CPU占用过高

可以观察到程序的CPU占用很高,接下来我们使用go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"来获取相关信息,其中的second参数表示取样时间,此处设置为10s。可以看到我们进入了一个交互界面。

接下来我们输入top即可查看占用CPU最高的几个调用。

其中每一列的的意思分别为:

列项 意思
flat 当前函数本身执行耗时
flat% flat占CPU总时间的比例
sum% 上面每一行的flat%总和
cum 当前函数本身加上其调用函数的总耗时
cum% cum占CPU总时间的比例

显然,CPU占用过高是由github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat造成的,接下来可用list来查找指定代码行,输入list Eat,查看代码具体问题:

可以观察到第24行有一个100亿次的空循环占用了大量的CPU时间,成功定位到问题。

在你的机器上安装graphviz后,通过输入web就可以看到图形化的调用栈信息:

图中可以很明显的看到tiger.(*Tiger).Eat特别大,表明它的CPU占用比较高。

通过输入exitq离开交互界面,注释掉该处代码,本小节结束。

内存占用过高

重新编译该程序,再次运行,可以发现,CPU占用已经降下来,但内存占用依旧很高。

使用命令go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"可以在8080端口打开一个web网页,页面如下:

通过该图可以发现mouse.(*Mouse).Steal占用了大量内存。

点击左上角的view,结果如下:

通过点击可以查看各种不同的视图,非常有用。此处不再展示各种视图,读者可自行查看。

点击其右边的sample,结果如下:

自上到下他们分别表示:程序累计申请的对象数、程序累计申请的内存大小、程序当前持有的对象数、程序当前占有的内存大小。同样本文不再展示。

通过source视图查看对应的代码,注释掉即可。

之后再次查看alloc_space,可以发现dog.(*Dog).Run分配了很多内存:

说明它一直在进行无意义的内存申请,申请完就被回收掉了。同样的方法定位到代码,注释掉即可。

协程泄露排查

虽然golang带有内存回收,一般不会发生内存泄露,但是是存在泄露的可能得。

在浏览器中可以看到,此时已经有一百多条协程。

这看上去似乎不是很正常,我们通过pprof排查一下。使用go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"命令打开浏览器,发现wolf.(*Wolf).Drink创建了100个协程。

还是老样子,在source视图下定位问题,然后注释掉。注意直接找可能不太好找,可以直接在搜索栏搜索。

注释掉之后,发现协程数恢复正常水平。

锁的争用

接下来排查锁的争用问题,老样子,输入go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"。可以发现wolf.(*Wolf).Howl的锁竞争问题严重。

同样的方式定位到错误。

可以发现,这个锁由主协程 Lock,并启动子协程去 Unlock,主协程会阻塞在第二次 Lock 这儿等待子协程完成任务,但由于子协程足足睡眠了一秒,导致主协程等待这个锁释放足足等了一秒钟。

虽然在具体的业务逻辑中可能存在这种情况,但在此处我们认为它是一个错误,注释掉它。

阻塞操作

接下来是最后一个问题,阻塞。

老样子,使用命令go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"查看阻塞情况。

可以发现,cat.(*Cat).Pee是发生了阻塞。

找到源码,发现是从一个 channel 里读数据时,发生了阻塞,直到这个 channel 在一秒后才有数据读出,这就导致程序阻塞了一秒而非睡眠了一秒。

注意到浏览器中显示有两个block,而我们只发现了一个block,在命令行可以发现另一个block,不展示的原因在于,在展示之前会设置一个阈值,只有高于这个阈值才会显示,所以过滤掉了另一个。这种设置使得我们在更复杂的程序中更方便定位问题,其实是有利的。

注释掉它,成功排查完成,恭喜你,成功解决了所有问题。

结语

相信通过本文,你已经对pprof有了一个基本的了解,但本文还是太浅显了,仅仅是一个入门而已,旨在消除你对pprof的陌生。希望你能通过进一步的学习熟练掌握pprof。

最后,受作者能力限制,如有错误或不清晰之处,还望体谅。

相关推荐
千慌百风定乾坤1 天前
Go 语言入门指南:基础语法和常用特性解析(下) | 豆包MarsCode AI刷题
青训营笔记
FOFO1 天前
青训营笔记 | HTML语义化的案例分析: 粗略地手绘分析juejin.cn首页 | 豆包MarsCode AI 刷题
青训营笔记
滑滑滑3 天前
后端实践-优化一个已有的 Go 程序提高其性能 | 豆包MarsCode AI刷题
青训营笔记
柠檬柠檬3 天前
Go 语言入门指南:基础语法和常用特性解析 | 豆包MarsCode AI刷题
青训营笔记
用户967136399653 天前
计算最小步长丨豆包MarsCodeAI刷题
青训营笔记
用户52975799354724 天前
字节跳动青训营刷题笔记2| 豆包MarsCode AI刷题
青训营笔记
clearcold4 天前
浅谈对LangChain中Model I/O的见解 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵4 天前
【字节青训营】 Go 进阶语言:并发概述、Goroutine、Channel、协程池 | 豆包MarsCode AI刷题
青训营笔记
用户336901104445 天前
数字分组求和题解 | 豆包MarsCode AI刷题
青训营笔记
dnxb1235 天前
GO语言工程实践课后作业:实现思路、代码以及路径记录 | 豆包MarsCode AI刷题
青训营笔记