Go 很适宜用来开发高性能网络应用,但依然须要借助无效的工具进行性能剖析,优化代码逻辑。本文介绍了如何通过 go test benchmark 和 pprof 进行性能剖析,从而实现最优的代码效力。原文: Profiling Go Applications in the Right Way with Examples
什么是性能剖析?
性能剖析 (Profiling) 是剖析应用程序从而辨认妨碍利用性能的瓶颈的根本技术,有助于检测代码的哪些局部 执行工夫太长 或耗费太多资源(如 CPU 和内存)。
分析方法
有三种分析方法。
- Go test(蕴含基准测试)
- 基于 runtime/pprof 的 运行时剖析
- 基于 net/http/pprof 的Web 剖析
剖析类型
- CPU (收集应用程序 CPU 应用状况的数据)
- 堆(Heap)/ 内存(Memory) (收集应用程序内存应用状况的数据)
- Goroutine (辨认创立最多 Goroutine 的函数)
- 阻塞 (辨认阻塞最多的函数)
- 线程 (辨认创立线程最多的函数)
- 互斥锁 (辨认有最多锁竞争的函数)
本文将次要关注应用上述办法进行 CPU 和内存剖析。
1. 基准测试(Benchmarking)
我想实现驰名的两数之和算法,这里不关注实现细节,间接运行:
go test -bench=.
-bench 参数运行我的项目中的所有基准测试。
依据下面的输入,与其余办法相比,TwoSumWithBruteForce
是最无效的办法。别忘了后果取决于函数输出,如果输出一个大数组,会失去不同的后果。😅
如果输出 go help testflag
,将看到许多参数及其解释,比方count
、benchtime
等,前面将解释最罕用的参数。
- 如果要运行特定函数,能够通过如下形式指定:
go test -bench='BenchmarkTwoSumWithBruteForce'
- 默认状况下,基准测试函数只运行一次。如果要自定义,能够应用
count
参数。例如,
go test -bench='.' -count=2
输入如下所示。
- 默认状况下,Go 决定每个基准测试操作的运行工夫,能够通过自定义
benchtime='2s'
指定。
能够同时应用 count
和benchtime
参数,以便更好的度量基准函数。请参考 How to write benchmarks in Go。
示例代码请参考 Github。
在事实世界中,函数可能既简单又长,计时毫无作用,因而须要提取 CPU 和内存剖析文件以进行进一步剖析。能够输出
go test -bench='.' -cpuprofile='cpu.prof' -memprofile='mem.prof'
而后通过 pprof 工具对其进行剖析。
1.1 CPU 剖析
如果输出
go tool pprof cpu.prof
并回车,就会看到 pprof 交互式控制台。
咱们来看看最次要的内容。
- 输出
top15
查看执行期间排名前 15 的资源密集型函数。(15 示意显示的节点数。)
为了解释分明,假如有一个 A
函数。
func A() {B() // 耗时 1s
DO STH DIRECTLY // 耗时 4s
C() // 耗时 6s}
flat 值和 cum 值计算为: flat 值为 A =4, cum 值为 A =11(1s + 4s + 6s)。
- 如果要基于 cum 进行排序,能够键入
top15 -cum
。也能够别离应用sort=cum
和top15
命令。 - 如果通过
top
取得更具体的输入,能够指定granularity
选项。例如,如果设置granularity=lines
,将显示函数的行。
得益于此,咱们能够辨认导致性能问题的函数的特定行。😌
- 输入还显示了运行时函数和用户自定义函数。如果只想关注本人的函数,能够设置
hide=runtime
并再次执行top15
。
能够通过输出 hide=
来重置。
- 此外,能够应用
show
命令。例如,输出show=TwoSum
- 如果只关注指定节点,能够应用
focus
命令。例如关注TwoSumOnePassHashTable
,显示为
能够输出 focus=
来重置。
- 如果须要获取该性能的详细信息,能够应用
list
命令。例如,想取得对于TwoSumWithTwoPassHashTable
函数的详细信息,输出list TwoSumWithTwoPassHashTable
- 如果要查看图形化的调用栈,能够键入
web
。
前面将提供更多对于剖析图表的细节。
- 还能够键入
gif
或pdf
以与别人共享相应格局的剖析数据。😃
1.2 内存剖析
如果输出 go tool pprof mem.prof
并回车
留神,下面提到的 flat 和 cum 是雷同的货色,只是测量不同的货色(CPU 单位 ms,内存单位 MB)。
- list 命令
- web 命令
能够应用 CPU 剖析局部中提到的所有命令。
上面看一下另一个办法,runtime/pprof。🚀
2. 基于 runtime/pprof 的运行时剖析
基准测试对单个函数的性能很有用,但不足以了解整体状况,这时就须要用到 runtime/pprof💠。
2.1 CPU 剖析
基准测试内置 CPU 和内存剖析,但如果须要让应用程序反对运行时 CPU 剖析,必须首先显示启用。
如果执行 go run .
,将看到生成的cpu.prof
文件,能够通过基准测试局部提到的 go tool pprof cpu.prof
对齐进行剖析。
本节将介绍我最喜爱的个性之一 pprof.Labels
。 此个性仅实用于 CPU 和 goroutine 剖析。
如果要向特定函数增加一个或多个标签,能够应用 pprof.Do
函数。
pprof.Do(ctx, pprof.Labels("label-key", "label-value"), func(ctx context.Context) {// 执行标签代码})
例如,
在 pprof 交互式控制台中,键入tags
,将显示带了有用信息的标记函数。
能够用标签做很多事件,浏览 Profiler labels in Go 能够取得更多信息。
pprof 还有很棒的 web 界面,容许咱们应用各种可视化形式剖析数据。
输出 go tool pprof -http=:6060 cpu.prof
,localhost:6060
将被关上。(为了更分明,我去掉了 pprof.Labels)
让咱们深入探讨图形示意。
节点色彩、字体大小、边缘粗细等都有不同含意,参考 pprof: Interpreting the Callgraph 获取更多细节。可视化使咱们可能更容易辨认和修复性能问题。
单击图中的节点,能够对其进行细化,咱们能够依据本人的抉择对可视化进行过滤。上面展现了局部内容 (focus、hide 等)。
还能够看到其余可视化选项。
下面呈现了 peek 和 source(作为 list 命令),因而上面将介绍火焰图(Flame Graph)”)。火焰图提供了代码工夫破费的高级视图。
每个函数都用一个黑白矩形示意,矩形的宽度与该函数破费的工夫成正比。
能够拜访 Github 获取源码。
2.2 内存剖析
如果须要向应用程序增加运行时内存剖析,必须显式启用。
能够拜访 Github 获取源码。
如果执行 go run .
,将看到生成的mem.prof
文件,能够用之前基准测试局部提到的 go tool pprof mem.prof
对齐进行剖析。
上面将介绍两个更有用的命令 tree
和peek
。
tree
显示了执行流的所有调用者和被调用者。
从而帮忙咱们辨认执行流并找出耗费最多内存的对象。(不要遗记应用granularity=lines
,它提供了更可读的格局。)
- 如果心愿查看特定函数的执行流程,能够应用
peek
命令。例如,peek expensiveFunc
显示如下
- 还能够应用 pprof web 界面进行内存剖析。输出
go tool pprof -http=:6060 mem.prof
,关上localhost:6060
。
3. 基于 net/http/pprof 的 Web 剖析
runtime/pprof包提供了 Go 程序性能剖析的低级接口,而 net/http/pprof 为剖析提供了更高级的接口,容许咱们通过 HTTP💃收集程序剖析信息,所须要做的就是:
输出localhost:5555/debug/pprof
,就能在浏览器上看到所有可用的剖析文件。如果没有应用 stdlib,能够查看 fiber、gin 或 echo 的 pprof 实现。
文档里记录了所有用法和参数,咱们看一下最罕用的。
获取 CPU 剖析数据及技巧
go tool pprof http://localhost:5555/debug/pprof/profile?seconds=30
在 CPU 剖析期间,请留神
runtime.mallogc
→ 示意能够优化小堆调配的数量。
syscall.Read
或者syscall.Write
→ 示意应用程序在内核模式下破费了大量工夫,为此能够尝试 I / O 缓冲。
获取堆 (采样沉闷对象内存调配) 剖析数据及技巧
go tool pprof http://localhost:5555/debug/pprof/heap
go tool pprof http://localhost:5555/debug/pprof/heap?gc=1
就我集体而言,我喜爱用 GC 参数诊断问题。例如,如果应用程序有内存透露问题,能够执行以下操作:
- 触发 GC(浏览器拜访 /debug/pprof/heap?gc=1)
- 下载堆数据,假如下载文件名为 file1
- 期待几秒或几分钟
- 再次触发 GC(浏览器拜访 /debug/pprof/heap?gc=1)
- 再次下载堆数据,假如下载文件名为 file2
- 应用 diff_base 进行比拟
go tool pprof -http=:6060 -diff_base file2 file1
获取内存调配 (抽样过来所有的内存调配) 剖析数据及技巧
go tool pprof http://localhost:5555/debug/pprof/allocs
在内存调配剖析期间,能够这样做
- 如果看到
bytes.growSlice
,应该思考应用sync.Pool
。 - 如果看到自定义函数,请查看是否在切片或映射中定义了固定容量。
延长浏览
- pprof Github Readme
- Profiling Go Programs by Russ Cox
- pprof man page
- GopherCon 2019: Dave Cheney — Two Go Programs, Three Different Profiling Techniques
- GopherCon 2021: Felix Geisendörfer — Go Profiling and Observability from Scratch
- GopherConAU 2019 — Alexander Else — Profiling a go service in production
- Practical Go Lessons Profiling Chapter
你好,我是俞凡,在 Motorola 做过研发,当初在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓重的趣味,平时喜爱浏览、思考,置信继续学习、一生成长,欢送一起交流学习。为了不便大家当前能第一工夫看到文章,请敌人们关注公众号 ”DeepNoMind”,并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的反对和能源,激励我继续写下去,和大家独特成长提高!
本文由 mdnice 多平台公布