Go很适合用来开发高性能网络应用,但仍然需要借助有效的工具进行性能分析,优化代码逻辑。本文介绍了如何通过go test benchmark和pprof进行性能分析,从而实现最优的代码效能。原文: Profiling Go Applications in the Right Way with Examples[1]
性能分析(Profiling) 是分析应用程序从而识别阻碍应用性能的瓶颈的基本技术,有助于检测代码的哪些部分执行时间太长或消耗太多资源(如CPU和内存)。
有三种分析方法。
本文将主要关注使用上述方法进行CPU和内存分析。
我想实现著名的两数之和算法[5],这里不关注实现细节,直接运行:
go test -bench=.
-bench参数运行项目中的所有基准测试。
根据上面的输出,与其他方法相比,TwoSumWithBruteForce
是最有效的方法。别忘了结果取决于函数输入,如果输入一个大数组,会得到不同的结果。
如果输入go help testflag
,将看到许多参数及其解释,比如count
、benchtime
等,后面将解释最常用的参数。
go test -bench='BenchmarkTwoSumWithBruteForce'
count
参数。例如, go test -bench='.' -count=2
输出如下所示。
benchtime='2s'
指定。 可以同时使用count
和benchtime
参数,以便更好的度量基准函数。请参考How to write benchmarks in Go[6]。
示例代码请参考Github[7]。
在现实世界中,函数可能既复杂又长,计时毫无作用,因此需要提取CPU和内存分析文件以进行进一步分析。可以输入
go test -bench='.' -cpuprofile='cpu.prof' -memprofile='mem.prof'
然后通过pprof[8]工具对其进行分析。
如果输入
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) 。
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
以与他人共享相应格式的分析数据。 如果输入go tool pprof mem.prof
并回车
注意,上面提到的flat和cum是相同的东西,只是测量不同的东西(CPU单位ms,内存单位MB)。
可以使用CPU分析部分中提到的所有命令。
下面看一下另一个方法,runtime/pprof。
基准测试对单个函数的性能很有用,但不足以理解整体情况,这时就需要用到runtime/pprof。
基准测试内置CPU和内存分析,但如果需要让应用程序支持运行时CPU分析,必须首先显示启用。
如果执行go run .
,将看到生成的cpu.prof
文件,可以通过基准测试部分提到的go tool pprof cpu.prof
对齐进行分析。
本节将介绍我最喜欢的特性之一pprof.Labels
。此特性仅适用于CPU和goroutine分析[10]。
如果要向特定函数添加一个或多个标签,可以使用pprof.Do
函数。
pprof.Do(ctx, pprof.Labels("label-key", "label-value"), func(ctx context.Context) {
// 执行标签代码
})
例如,
在pprof交互式控制台中,键入tags
,将显示带了有用信息的标记函数。
可以用标签做很多事情[11],阅读Profiler labels in Go[12]可以获得更多信息。
pprof还有很棒的web界面,允许我们使用各种可视化方式分析数据。
输入go tool pprof -http=:6060 cpu.prof
,localhost:6060
将被打开。 (为了更清楚,我去掉了pprof.Labels)
让我们深入探讨图形表示。
节点颜色、字体大小、边缘粗细等都有不同含义,参考pprof: Interpreting the Callgraph[13]获取更多细节。可视化使我们能够更容易识别和修复性能问题。
单击图中的节点,可以对其进行细化,我们可以根据自己的选择对可视化进行过滤。下面展示了部分内容(focus、hide等)。
还可以看到其他可视化选项。
上面出现了peek和source(作为list命令),因此下面将介绍火焰图(Flame Graph)[14]。火焰图提供了代码时间花费的高级视图。
每个函数都用一个彩色矩形表示,矩形的宽度与该函数花费的时间成正比。
可以访问Github[15]获取源码。
如果需要向应用程序添加运行时内存分析,必须显式启用。
可以访问Github[16]获取源码。
如果执行go run .
,将看到生成的mem.prof
文件,可以用之前基准测试部分提到的go tool pprof mem.prof
对齐进行分析。
下面将介绍两个更有用的命令tree
和peek
。
tree
显示了执行流的所有调用者和被调用者。 从而帮助我们识别执行流并找出消耗最多内存的对象。 (不要忘记使用granularity=lines
,它提供了更可读的格式。)
peek
命令。例如, peek expensiveFunc
显示如下 go tool pprof -http=:6060 mem.prof
,打开 localhost:6060
。 runtime/pprof包提供了Go程序性能分析的低级接口,而net/http/pprof为分析提供了更高级的接口,允许我们通过HTTP收集程序分析信息,所需要做的就是:
输入localhost:5555/debug/pprof
,就能在浏览器上看到所有可用的分析文件。如果没有使用stdlib,可以查看fiber[18]、gin[19]或echo[20]的pprof实现。
文档里记录了所有用法和参数[21],我们看一下最常用的。
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参数诊断问题。例如,如果应用程序有内存泄漏问题,可以执行以下操作:
go tool pprof -http=:6060 -diff_base file2 file1
go tool pprof http://localhost:5555/debug/pprof/allocs
在内存分配分析期间,可以这样做
bytes.growSlice
,应该考虑使用 sync.Pool
。 参考资料 [1]你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
Profiling Go Applications in the Right Way with Examples: https://blog.stackademic.com/profiling-go-applications-in-the-right-way-with-examples-e784526e9481
[2]runtime/pprof: https://pkg.go.dev/runtime/pprof
[3]net/http/pprof: https://pkg.go.dev/net/http/pprof
[4]Resource Contention: https://en.wikipedia.org/wiki/Resource_contention
[5]Two Sum Algorithm: https://leetcode.com/problems/two-sum
[6]How to write benchmarks in Go: https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go
[7]pprof-example: https://github.com/Abdulsametileri/pprof-examples/tree/main/benchmarking
[8]pprof: https://linux.die.net/man/1/pprof
[9]runtime/pprof: https://pkg.go.dev/runtime/pprof
[10]pprof.Labels: https://pkg.go.dev/runtime/pprof#Labels
[11]pprof tags: https://github.com/google/pprof/blob/main/doc/README.md#tags
[12]Profiler labels in Go: https://rakyll.org/profiler-labels
[13]pprof: Interpreting the Callgraph: https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph
[14]火焰图(Flame Graph): https://github.com/google/pprof/blob/main/doc/README.md#flame-graph
[15]runtime pprof cpu example: https://github.com/Abdulsametileri/pprof-examples/tree/main/runtimepprof/cpu
[16]runtime pprof memory example: https://github.com/Abdulsametileri/pprof-examples/tree/main/runtimepprof/mem
[17]net/http/pprof: https://pkg.go.dev/net/http/pprof
[18]fiber pprof: https://docs.gofiber.io/api/middleware/pprof
[19]gin pprof: https://github.com/gin-contrib/pprof
[20]echo pprof: https://pkg.go.dev/github.com/labstack/echo-contrib/pprof
[21]net/http/pprof usage examples: https://pkg.go.dev/net/http/pprof#hdr-Usage_examples
[22]pprof comparing profiles: https://github.com/google/pprof/blob/main/doc/README.md#comparing-profiles
[23]pprof Github Readme: https://github.com/google/pprof/blob/main/doc/README.md
[24]Profiling Go Programs by Russ Cox: https://blog.golang.org/2011/06/profiling-go-programs.html
[25]pprof man page: https://linux.die.net/man/1/pprof
[26]GopherCon 2019: Dave Cheney — Two Go Programs, Three Different Profiling Techniques: https://www.youtube.com/watch?v=nok0aYiGiYA
[27]GopherCon 2021: Felix Geisendörfer — Go Profiling and Observability from Scratch: https://www.youtube.com/watch?v=7hg4T2Qqowk
[28]GopherConAU 2019 — Alexander Else — Profiling a go service in production: https://www.youtube.com/watch?v=19bxBMPOlyA
[29]Practical Go Lessons Profiling Chapter: https://www.practical-go-lessons.com/chap-36-program-profiling
本文由 mdnice 多平台发布