Golang-Pprof-转

2022/09/26 Golang

转 - https://blog.wolfogre.com/posts/go-ppof-practice/

本文由 简悦 SimpRead 转码, 原文地址 blog.wolfogre.com

如果要说在 golang 开发过程进行性能调优,pprof 一定是一个大杀器般的工具。

前言

如果要说在 golang 开发过程进行性能调优,pprof 一定是一个大杀器般的工具。但在网上找到的教程都偏向简略,难寻真的能应用于实战的教程。这也无可厚非,毕竟 pprof 是当程序占用资源异常时才需要启用的工具,而我相信大家的编码水平和排场问题的能力是足够高的,一般不会写出性能极度堪忧的程序,且即使发现有一些资源异常占用,也会通过排查代码快速定位,这也导致 pprof 需要上战场的机会少之又少。即使大家有心想学习使用 pprof,却也常常相忘于江湖。

既然如此,那我就送大家一个性能极度堪忧的 “炸弹” 程序吧!

这程序没啥正经用途缺极度占用资源,基本覆盖了常见的性能问题。本文就是希望读者能一步一步按照提示,使用 pprof 定位这个程序的的性能瓶颈所在,借此学习 pprof 工具的使用方法。

因此,本文是一场 “实验课” 而非“理论课”,请读者腾出时间,脚踏实地,一步一步随实验步骤进行操作,这会是一个很有趣的冒险,不会很无聊,希望你能喜欢。

实验准备

这里假设你有基本的 golang 开发功底,拥有 golang 开发环境并配置了 $GOPATH,能熟练阅读简单的代码或进行简单的修改,且知道如何编译运行 golang 程序。此外,需要你大致知道 pprof 是干什么的,有一个基本印象即可,你可以花几分钟时间读一下《Golang 大杀器之性能剖析 PProf》的开头部分,这不会耽误太久。

此外由于你需要运行一个 “炸弹” 程序,请务必确保你用于做实验的机器有充足的资源,你的机器至少需要:

  • 2 核 CPU;
  • 2G 内存。

注意,以上只是最低需求,你的机器配置能高于上述要求自然最好。实际运行 “炸弹” 时,你可以关闭电脑上其他不必要的程序,甚至 IDE 都不用开,我们的实验操作基本上是在命令行里进行的。

此外,务必确保你是在个人机器上运行 “炸弹” 的,能接受机器死机重启的后果(虽然这发生的概率很低)。请你务必不要在危险的边缘试探,比如在线上服务器运行这个程序。

可能说得你都有点害怕了,为打消你顾虑,我可以剧透一下 “炸弹” 的情况,让你安心:

  • 程序会占用约 2G 内存;
  • 程序占用 CPU 最高约 100%(总量按 “核数 * 100%” 来算);
  • 程序不涉及网络或文件读写;
  • 程序除了吃资源之外没有其他危险操作。

且程序所占用的各类资源,均不会随着运行时间的增长而增长,换句话说,只要你把 “炸弹” 启动并正常运行了一分钟,就基本确认安全了,之后即使运行几天也不会有更多的资源占用,除了有点费电之外。

获取 “炸弹”

炸弹程序的代码我已经放到了 GitHub 上,你只需要在终端里运行 go get 便可获取,注意加上 -d 参数,避免下载后自动安装:

go get -d github.com/wolfogre/go-pprof-practice
cd $GOPATH/src/github.com/wolfogre/go-pprof-practice

我们可以简单看一下 main.go 文件,里面有几个帮助排除性能调问题的关键的的点,我加上了些注释方便你理解,如下:

package main

import (
	// 略
	_ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告
	// 略
)

func main() {
	// 略

	runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载
	runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
	runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪

	go func() {
		// 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了
		if err := http.ListenAndServe(":6060", nil); err != nil {
			log.Fatal(err)
		}
		os.Exit(0)
	}()

	// 略
}

除此之外的其他代码你一律不用看,那些都是我为了模拟一个 “逻辑复杂” 的程序而编造的,其中大多数的问题很容易通过肉眼发现,但我们需要做的是通过 pprof 来定位代码的问题,所以为了保证实验的趣味性请不要提前阅读代码,可以实验完成后再看。

接着我们需要编译一下这个程序并运行,你不用担心依赖问题,这个程序没有任何外部依赖。

go build
./go-pprof-practice

运行后注意查看一下资源是否吃紧,机器是否还能扛得住,坚持一分钟,如果确认没问题,咱们再进行下一步。

控制台里应该会不停的打印日志,都是一些 “猫狗虎鼠在不停地吃喝拉撒” 的屁话,没有意义,不用细看。

使用 pprof

保持程序运行,打开浏览器访问 http://localhost:6060/debug/pprof/,可以看到如下页面:

页面上展示了可用的程序运行采样数据,分别有:

类型描述备注
allocs内存分配情况的采样信息可以用浏览器打开,但可读性不高
blocks阻塞操作情况的采样信息可以用浏览器打开,但可读性不高
cmdline显示程序启动命令及参数可以用浏览器打开,这里会显示 ./go-pprof-practice
goroutine当前所有协程的堆栈信息可以用浏览器打开,但可读性不高
heap堆上内存使用情况的采样信息可以用浏览器打开,但可读性不高
mutex锁争用情况的采样信息可以用浏览器打开,但可读性不高
profileCPU 占用情况的采样信息浏览器打开会下载文件
threadcreate系统线程创建情况的采样信息可以用浏览器打开,但可读性不高
trace程序运行跟踪信息浏览器打开会下载文件,本文不涉及,可另行参阅《深入浅出 Go trace》

因为 cmdline 没有什么实验价值,trace 与本文主题关系不大,threadcreate 涉及的情况偏复杂,所以这三个类型的采样信息这里暂且不提。除此之外,其他所有类型的采样信息本文都会涉及到,且炸弹程序已经为每一种类型的采样信息埋藏了一个对应的性能问题,等待你的发现。

由于直接阅读采样信息缺乏直观性,我们需要借助 go tool pprof 命令来排查问题,这个命令是 go 原生自带的,所以不用额外安装。

我们先不用完整地学习如何使用这个命令,毕竟那太枯燥了,我们一边实战一边学习。

以下正式开始。

排查 CPU 占用过高

我们首先通过活动监视器(或任务管理器、top 命令,取决于你的操作系统和你的喜好),查看一下炸弹程序的 CPU 占用: