Go 库性能分析工具pprof

 更新时间:2022年12月15日 10:50:06   作者:小马别过河  
这篇文章主要为大家介绍了Go 库性能分析工具pprof,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

场景

我们一般没必要过度优化 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行需要优化。

总结

pprof 还有很多玩法,包括其他的性能指标,go tool 的其他命令,profile 文件的可视化等。这个留给读者自行扩展阅读。

本文主要参考了 Russ Cox 大神的文章:《Profiling Go Programs》 (go.dev/blog/pprof)… 文章为反驳 "Go性能不如其他语言"的观点,借助 pprof 大幅度优化了程序的运行时间和内存。

以上就是Go 库性能分析工具pprof的详细内容,更多关于Go pprof性能分析的资料请关注脚本之家其它相关文章!

相关文章

  • GO语言异常处理机制panic和recover分析

    GO语言异常处理机制panic和recover分析

    这篇文章主要介绍了GO语言异常处理机制panic和recover,分析了捕获运行时发生错误的方法,是非常实用的技巧,需要的朋友可以参考下
    2014-12-12
  • go语言打包的网页wasm示例详解

    go语言打包的网页wasm示例详解

    这篇文章主要为大家介绍了go打包网页wasm示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
    2022-09-09
  • Go官方工具链用法详解

    Go官方工具链用法详解

    Go官方工具链工具要求所有的Go源代码文件必须以.go后缀结尾。这里,我们假设一个最简单的Go程序放在hello.go的文件中,下面通过示例代码给大家介绍Go官方工具链用法简介,需要的朋友可以参考下
    2021-10-10
  • 探索Go语言中的switch高级用法

    探索Go语言中的switch高级用法

    在Go语言中,switch语句除了常见的用法外,还有一种不常用但有趣的写法,这种写法中,switch后面不跟任何表达式,而每个case后面跟的是返回bool类型的函数调用表达式,这实际上是一个等价于switch true的用法,通过从上到下逐一比较case后的表达式是否为true来决定执行哪个分支
    2024-10-10
  • Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法

    Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法

    对音视频一直是小白,决定沉下心来,好好研究一下音视频知识,下面这篇文章主要给大家介绍了关于Go语言学习之将mp4通过rtmp推送流媒体服务的实现方法,需要的朋友可以参考下
    2022-12-12
  • Golang使用泛型对数组进行去重的实现

    Golang使用泛型对数组进行去重的实现

    本文主要介绍了Golang使用泛型对数组进行去重的实现,通过使用类型参数T和类型约束any,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-02-02
  • Go 1.21.0 新增结构化日志记录标准库log/slog使用详解

    Go 1.21.0 新增结构化日志记录标准库log/slog使用详解

    这篇文章主要为大家介绍了Go 1.21.0 新增结构化日志记录标准库log/slog使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • Golang精编49面试题汇总(选择题)

    Golang精编49面试题汇总(选择题)

    这篇文章主要介绍了Golang精编49面试题汇总(选择题),本文章内容详细,具有很好的参考价值,希望对大家有所帮助,需要的朋友可以参考下
    2023-01-01
  • Go标准库http与fasthttp服务端性能对比场景分析

    Go标准库http与fasthttp服务端性能对比场景分析

    这篇文章主要介绍了Go标准库http与fasthttp服务端性能比较,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06
  • 更高效的GoLevelDB:shardingdb实现分片和并发读写操作

    更高效的GoLevelDB:shardingdb实现分片和并发读写操作

    这篇文章主要介绍了更高效的GoLevelDB:shardingdb实现分片和并发读写操作的相关资料,需要的朋友可以参考下
    2023-09-09

最新评论