pprof优化实战|青训营

这是青训营笔记的第五篇文章。

前言

本文主要内容为Go性能调优、性能分析工具------pprof的介绍、pprof的采样过程以及原理解析。

简介

在实际项目中,我们都知道优化很重要,在工作中伴随着迭代的开发,代码的优化要适度进行不要过度,下面是优化的原则可供参考。

性能优化、调优原则:

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

性能分析工具

我们希望知道应用在什么地方耗费了多少CPUMemory,有没有什么工具可以检测?

pprof就是这样的工具,pprof可用于可视化和分析性能分析数据的工具,下面将对pprof进行介绍以及简单的使用介绍。

pprof是一个强大的性能分析工具,可以捕捉到多维度的运行状态的数据,Go在语言层面集成了profile采样工具,在程序运行过程中可以获取cpuheapblocktraces等执行信息,这些会涉及到runtime/pprofnet/http/pprof等包,获取profile数据最有两种形式:web形式与profile文件生成形式。

pprof实战

项目搭建

搭建pprof实践项目,这里使用的是github上一个开源项目,这个项目提前埋入了一些炸弹代码,产生可观测的性能问题,运行该项目会占用1CPU核心和超过1GB的内存。

开源项目地址:github.com/wolfogre/go...

将项目clone下来之后,在终端输入go run main.go即可运行项目。

指标查看

打开浏览器,输入地址 http://localhost:6060/debug/pprof 即可,如图。

从页面中可以查到,内存、block、协程、堆等分配的情况以及锁的操作等。

CPU

首先看一下CPU的运行情况,打开电脑自带的活动管理器,可以看到此项目在我的电脑上占了8.6%的CPU。

pprof在每运行一段时间后,就会把运行信息输出到文件中,我们可以通过命令来获取文件。

在终端中输入命令,获取文件:

go 复制代码
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
topN

命令:top

用于查看占用资源最多的函数

参数说明:

  • flat: 当前函数本身的执行耗时
  • flat%flatCPU总时间的比例
  • sum%: 上面每一行的flat%总和
  • cum: 当前函数本身加上其周期函数的总耗时
  • cum%cumCPU总时间的比例

从图中可以看到,Flat列有的是0,有的不是0,那么Flat在什么情况下等于Cum?什么情况下等于0呢?

根据上述参数说明,可以知道:

  • Flat == Cum,说明函数中没有调用其他函数
  • Flat == 0,说明函数中只有其他函数的调用

通过top命令,得出Eat函数是最消耗CPU的,下面介绍如何定位到代码的具体行。

list

命令:list

可以根据指定的正则表达式查找代码行

输入命令list Eat,从输出结果里可以看到对应的文件为tiger.go,而且具体的代码行为24行的for循环。

web

有时候根据命令行查看结果不是很直观,pprof还提供了可视化的查看方式。

命令: web

将调用关系生成可视化文件,可以输出可视化调用量以及每个节点的资源占用数据。

找到代码进行更改,将循环次数改小一些,再次启动项目,查看资源占用情况,可以发现CPU已经没有那么高了。

Heap

经过代码的改造,可以发现CPU的问题已经解决了,但是内存使用还是很高。

这次直接以可视化的方法查看,输入以下命令,打开堆内存的占用情况:

ini 复制代码
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

打开之后,会出现一下界面:

从上图我们一眼就可以看出,Mouse这个对象的Steal方法占用的内存最多。

打开View->Source视图,还可以看到具体的代码文件以及对应的行数。

找到对应的代码,将代码进行注释,然后看下资源占用情况。

注释掉mouse.go文件的第50行。

重新运行项目,查看资源占用已经很低了。

采样说明,SAMPLE菜单中有几个选项,对应说明如下:

  • alloc_objects: 程序累计申请的对象数
  • alloc_space: 程序累计申请的内存大小
  • inuse_objects: 程序当前持有的对象数
  • inuse_space: 程序当前占用的内存大小

协程

goroutine泄露也会导致内存泄露

以可视化的方法查看,输入以下命令,打开堆内存的占用情况:

ini 复制代码
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

火焰图说明:

  • 由上到下标识调用顺序
  • 每一块代表一个函数,越长代表占用CPU时间更长
  • 火焰图是动态的,支持点击块进行分析

切换到Source视图,查看代码具体位置。

可以看出wolf.go文件里的第34行,协程里进行了睡眠30s,修改此处代码即可修复该问题。

mutex

通过pprof查看锁相关问题,比如加锁时间长等。

执行命令:

ini 复制代码
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"

输出界面如下:

切换到代码视图:

可以发现,wolf.go文件里在释放锁前,进行了睡眠操作,对此处代码进行修改即可修复问题。

block

检查代码中是否存在阻塞,执行命令:

ini 复制代码
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"

输出界面如下:

切换到Source视图,可以看到cat.go文件里存在阻塞行为:

同样的,通过top命令也可以看到存在问题的文件。

pprof原理

下面介绍一下pprof的采样过程以及实现原理。

CPU

采样对象:函数调用和他们占用的时间

采样率:100次/秒,固定值

采样时间:从手动启动到手动结束

采样详细流程如下:

流程说明:

  • 操作系统:每10ms向进程发送一次SIGPROF信号
  • 进程:每次接收到SIGPROF信号会记录调用堆栈
  • 写缓冲:每100ms读取已经记录的调用栈并写入输出流

堆内存

堆内存采样过程如下:

  • 采样程序通过内存分配器在堆上分配和释放内存,记录分配/释放的大小和数量
  • 采样率:每分配512KB记录一次,可在运行开头修改,1为每次分配均记录
  • 采样时间:从程序运行开始到采样时
  • 采样指标:alloc_space, alloc_objects, inuse_space, inuse_objects
  • 计算方式:inuse = alloc - free

协程&线程创建

协程和线程创建在实现上非常的相似,都是会在STW之后,遍历所有goroutine/所有线程的列表并输出堆栈,最后Start The World继续运行。这个采样是立刻触发的全量记录,可以通过比较两个时间点的差值来得到某一时间段的指标。

协程(Goroutine)

记录所有用户发起且在运行中的goroutine(即入口非runtime开头的)runtime.main的调用栈信息

线程创建(ThreadCreate)

记录程序创建的所有系统线程的信息

协程和线程创建的采样流程如下:

Block&Mutex

阻塞和锁竞争这两个指标在流程和原理也非常相似,这两个采样记录的都是对应操作发生的调用栈、次数和耗时,不过这两个指标的采样率含义并不相同。

阻塞操作的采样率是一个"阈值",消耗超过阈值时间的阻塞操作才会被记录,1为每次操作都会记录。

锁竞争的采样率是一个"比例",运行时会通过随机数来只记录固定比例的锁操作,1为每次操作都会记录。

阻塞操作的采样操作:

  • 采样阻塞操作的次数和耗时
  • 采样率:阻塞耗时超过阈值的才会被记录,1为每次阻塞均记录

锁竞争的采样操作:

  • 采样争抢锁的次数和耗时
  • 采样率:只记录固定比例的锁操作,1为每次加锁均记录

阻塞和锁竞争的采样流程如下:

总结

本文主要内容为Go性能调优、性能分析工具------pprofpprof提供了命令行、可视化等排查问题手段,本文通过代码实际运行排查代码中存在的性能问题,通过pprof可以方便快捷的排查代码中出现的CPU、内存、堆栈、协程、block、锁等各个方面的问题。

虽然例子比较简单,但是通过这个例子可以使我们快速上手pprof工具的使用方法,为之后排查问题、性能优化奠定了良好的基础。

相关推荐
Find24 天前
MaxKB 集成langchain + Vue + PostgreSQL 的 本地大模型+本地知识库 构建私有大模型 | MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 14.数组元素之和最小化 | 豆包MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 25.DNA序列编辑距离 | 豆包MarsCode AI刷题
青训营笔记
理tan王子24 天前
伴学笔记 AI刷题 9.超市里的货物架调整 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵1 个月前
分而治之,主题分片Partition | 豆包MarsCode AI刷题
青训营笔记
三六1 个月前
刷题漫漫路(二)| 豆包MarsCode AI刷题
青训营笔记
tabzzz1 个月前
突破Zustand的局限性:与React ContentAPI搭配使用
前端·青训营笔记
Serendipity5651 个月前
Go 语言入门指南——单元测试 | 豆包MarsCode AI刷题;
青训营笔记
wml1 个月前
前端实践-使用React实现简单代办事项列表 | 豆包MarsCode AI刷题
青训营笔记
用户44710308932421 个月前
详解前端框架中的设计模式 | 豆包MarsCode AI刷题
青训营笔记