Go 库性能分析工具pprof

作者:小马别过河 时间:2024-02-13 20:55:27 

场景

我们一般没必要过度优化 Go 程序性能。但是真正需要时,Go 提供的 pprof 工具能帮我们快速定位到问题。比如,我们团队之前有一个服务,在本地和测试环境没问题,一到灰度环境,就报 cpu 负载过高,后经排查,发现某处代码死循环了。我把代码简化成如下:


// 处理某些业务,真实的代码中这个死循环很隐蔽
func retrieveSomeThing() {
for {}
}
// 处理其他的一些业务,无意义,用于后续做例子
func doSomeThing() {
do1()
for i := 0; i < 200000000; i++ {}
do2()
}
// 无意义
func do1() {
for i := 0; i < 200000000; i++ {}
}
// 无意义
func do2() {
for i := 0; i < 200000000; i++ {}
}
func main() {
go retrieveSomeThing()
 go doSomeThing()
// 阻塞一下
time.Sleep(3 * time.Second)
}

解决问题前,先介绍下 pprof。

pprof

pprof 包会输出运行时的分析数据(profiling data),这些数据可以被 pprof 的可视化工具解析。Go 标准库主要提供了两个包:

  • runtime/pprof 通过写入到文件的方式暴露 profile 数据;

  • net/http/pprof 通过 http 服务暴露 profile 数据,适用于守护进程。

生成 profile 文件

CPU 性能分析

runtime/pprof 中,使用StartCPUProfile开启 CPU 性能分析。退出程序前,需要调用StopCPUProfile把采样数据 flush 到输出文件。

采样的频率默认是 100 Hz(每秒 100 次)。

// 输出到标准输出,一般是指定文件
if err := pprof.StartCPUProfile(os.Stdout); err != nil {
   log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()

内存性能分析

调用 WriteHeapProfile 开启内存性能分析:

// 输出到标准输出,一般是指定文件
if err := pprof.WriteHeapProfile(os.Stdout); err != nil {
   log.Fatal("could not write memory profile: ", err)
}
}

分析 profile 文件 && 优化代码

以开篇的代码为例,由于是 CPU 过载,我们可以在 main 函数开启 CPU Profile:

// 通过参数指定 cpu profile 输出的文件
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
// 开启 CPU 分析
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
// 业务代码
go retrieveSomeThing()
 go doSomeThing()
// 模拟阻塞
time.Sleep(5 * time.Second)
}

我们执行命令,输出 profile 文件到 cpu.prof。

go run main.go -cpuprofile cpu.prof

go tool pprof

Go 提供性能解析工具:go tool pprof。我们使用 go tool 打开 profile 文件。

> go tool pprof cpu.prof
Type: cpu
Time: Nov 16, 2022 at 1:40pm (CST)
Duration: 5.17s, Total samples = 4.54s (87.75%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

这是个交互式的界面,输入help可以查看所有命令。

top 命令

我们使用 topN 命令,查看根据 flat 从大到小排序的前 N 条数据。

(pprof) top10
Showing nodes accounting for 4650ms, 100% of 4650ms total
     flat  flat%   sum%        cum   cum%
   4220ms 90.75% 90.75%     4450ms 95.70%  main.retrieveSomeThing
    230ms  4.95% 95.70%      230ms  4.95%  runtime.asyncPreempt
     80ms  1.72% 97.42%      200ms  4.30%  main.doSomeThing
     70ms  1.51% 98.92%       70ms  1.51%  main.do2 (inline)
     50ms  1.08%   100%       50ms  1.08%  main.do1 (inline)

top 命令返回数据有5个指标:

  • flat : 本函数占用的 CPU 时间,不包括调用函数的时间;

  • flat% : flat 占的百分比;

  • sum% : 前面 flat% 的总和;

  • cum : 累计时间,包括调用的函数的时间;

  • cum% : cum 的百分比。

main.doSomeThing(排第三的函数)为例子,耗时为:

func doSomeThing() {                   // flat: 80ms  cum: 200ms
do1()                                // 执行时间 50ms
for i := 0; i < 200000000; i++ {}    // 执行时间 80ms
do2()                                // 执行时间 70ms
}

doSomeThing 的 flat 的值为:

for i := 0; i < 200000000; i++ {}的执行时间(80ms),不包括do1和do2的时间。

doSomeThing 的 cum 的值为:

cum(200ms) = doSomething的flat(80ms) + do1的flat(50ms) + do2的flat(70ms)

ps: top 可以使用 -cum 参数来指定,根据 cum 排序。

list 命令

明白了 top 的指标的意思,我们关注到,排在 top1 的函数是 retrieveSomeThing。可以使用 list 命令,查看 retrieveSomeThing 耗时:

(pprof) list retrieveSomeThing
Total: 4.65s
ROUTINE ======================== main.retrieveSomeThing in /xxxx/pprof_note/pprof/main.go
    4.22s      4.45s (flat, cum) 95.70% of Total
     10ms       10ms      1:package main
        .          .      2:
        .          .      3:import (
        .          .      4:   "flag"
        .          .      5:   "log"
        .          .      6:   "os"
        .          .      7:   "runtime/pprof"
        .          .      8:   "time"
        .          .      9:)
        .          .     10:
        .          .     11:// 处理某些业务,真实的代码中这个死循环很隐蔽
        .          .     12:func retrieveSomeThing() {
    4.21s      4.44s     13:   for {
        .          .     14:   }
        .          .     15:}
        .          .     16:
        .          .     17:// 处理其他的一些业务,无意义,用于后续做例子
        .          .     18:func doSomeThing() {

我们定位到13行需要优化。

来源:https://juejin.cn/post/7166518730514497543

标签:Go,pprof,性能分析
0
投稿

猜你喜欢

  • Oracle时间日期操作方法小结第1/2页

    2010-11-29 19:40:00
  • python实现windows下文件备份脚本

    2021-05-06 06:32:41
  • mysql 字符串正则表达式及说明

    2024-01-13 17:47:59
  • Oracle数据库处理多媒体信息

    2010-07-16 13:01:00
  • 详解MySQL的用户密码过期功能

    2024-01-21 01:29:40
  • python 实现红包随机生成算法的简单实例

    2022-07-07 17:32:48
  • 数据库应用经验:如何简单安装MySQL数据库

    2009-01-04 12:58:00
  • Python安装selenium包详细过程

    2023-04-12 00:05:31
  • Python 调用 zabbix api的方法示例

    2021-07-15 02:35:09
  • selenium python浏览器多窗口处理代码示例

    2023-11-20 07:09:29
  • Python模块pexpect安装及使用流程

    2023-04-19 05:41:29
  • cv2.imread 和 cv2.imdecode 用法及区别

    2023-12-03 06:15:49
  • Joomla开启SEF的方法

    2024-04-30 08:47:39
  • python中slice参数过长的处理方法及实例

    2023-07-13 13:12:10
  • 利用windows任务计划实现oracle的定期备份

    2009-08-31 12:24:00
  • Python中最大最小赋值小技巧(分享)

    2021-08-26 06:07:44
  • vue实现百度搜索下拉提示功能实例

    2024-04-28 09:31:15
  • 用ASP动态生成JS表单验证代码

    2007-09-30 20:38:00
  • python编程Flask框架简单使用教程

    2023-12-25 20:23:33
  • Python学习之文件的创建与写入详解

    2021-03-07 09:27:06
  • asp之家 网络编程 m.aspxhome.com