GO语言性能分析工具 pprof|青训营笔记

105 阅读5分钟

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

1. 概述

我将主要介绍如下知识点:

  1. Go 语言性能分析工具 pprof
  2. pprof 排查实战

2. 性能分析工具 pprof

2.1 性能调优原则

在介绍实际优化手段之前,我们先来了解下性能调优原则,指导我们更合理的进行性能优化:

  • 要依靠数据而不是猜测
  • 要定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

2.2 pprof 功能简介

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

1674046623023.png

3. pprof 排查实战

3.1 搭建 pprof 实践项目

  • 可先克隆代码到本地,随后编译运行(会占用1CPU核心和超过1GB的内存)
  • 项目链接:github.com/wolfogre/go…
  • 项目提前埋入了一些炸弹代码,产生可观测的性能问题

3.2 浏览器查看指标

  • 在浏览器中打开 http://localhost:6060/debug/pprof

  • 可以看到如下页面: 这就是我们刚刚引入的net/http/pprof 注入的入口了

    1674047533587.png

页面上展示了可用的程序运行采样数据且有简单说明,分别是:

allocs:内存分配情况

blocks:阻塞操作情况

cmdline:程序启动命令

goroutine:当前所有goroutine的堆栈信息

heap:堆上内存使用情况(同alloc)

mutex:锁竞争操作情况

profile:CPU占用情况

threadcreate:当前所有创建的系统线程的堆栈信息 trace:程序运行跟踪信息

  • 点击 allocs 进去,看到如下页面,可以看出数据可读性很差,故我们一会儿借助 pprof 工具帮助阅读这些指标

    1674048666187.png

  • 由于是在 CPU,堆内存,goroutine,锁竞争和阻塞操作上埋了炸弹,所有可以使用 pprof 工具分析上述

    1674048958507.png

    因为 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 终端

    1674049181398.png

  • 输入 top 命令,可以查看CPU占用最高的函数,默认会展示资源占用最高的10个函数,如果只需要查看最高的N个函数(例如查看最高的3个调用,输入 top3)

    可以看到如下结果:

    1674049414870.png

    这五列从左到右分别是:

    1674049531617.png

    可以看到表格的第一行里,Tiger.Eat 函数本身占用3.12秒的CPU时间,占总时间的98.11%,显然问题就是这里引起的

  • 接着,输入list Eat 查找这个函数,看看具体是哪里出了问题(list 命令会根据后面给定的正则表达式查找代码),并按行展示出每一行的占用,可以看到,第24行有一个100亿次的空循环,占用了3.56秒的CPU时间,问题就在这儿了,定位成功

    1674050106629.png

3.4 Heap - 堆内存

注释 CPU 问题代码,重新运行后,打开活动监视器,可以发现进程的CPU已经降下来了,然而内存使用还是很高;接着我们来排查内存问题,

  • 同样的,在终端输入如下命令:
go tool pprof "http://localhost:6060/debug/pprof/heap"
  • 然后输入 top,可以明显看到,这里出问题的 *Mouse.Steal() 函数,它占用了 1.2GB 内存

    1674051720438.png

  • 最后输入 list Steal,如下页面,自行找到问题代码并注释即可

    1674051834847.png

3.5 goroutine - 协程

Golang 是一门自带垃圾回收的语言,一般情况下内存泄露是没那么容易发生的,但是有一种例外:goroutine是很容易泄露的,进而会导致内存泄露,所以接下来,我们来看看 goroutine 的使用情况,

  • 在终端输入如下命令:

    go tool pprof "http://localhost:6060/debug/pprof/goroutine"
    
  • 发现「炸弹」程序已经有一百多条 goroutine 在运行了,这个量级并不是很大,但对于一个简单的小程序来说还是很不正常的

    1674053887037.png

  • 可以看到,(*Wolf).Drink() 这个调用创建了超过90%的goroutine,输入 list Drink 即可定位问题代码,找到并注释即可

    1674053979707.png

4. 总结

剩下 阻塞 的问题相对来说实际工作不常遇到,这里就不再讲述,感兴趣的同学可自行上网查阅。

另外,其实有两种展示方式可视化终端和网页,四种视图Top、 Graph、 源码和火焰图,本篇文章只展示了可视化终端 和 Top,但是网页端的展示方式会更直观(大家后续可重点查阅下),笔者也还在学习的过程中....

参考:

  • 字节内部课:GO性能优化分析方式