# 采集数据
使用 Go 命令行工具生成性能数据文件:
- v1.test,测试生成的二进制文件,进行性能分析时用于解析各种符号。
- cpu.profile,CPU 性能数据文件。
- mem.profile,内存性能数据文件。
go test -benchtime=30s -benchmem -bench=".*" -cpuprofile cpu.profile -memprofile mem.profile
# goos: linux
# goarch: amd64
# pkg: github.com/marmotedu/iam/internal/apiserver/service/v1
# cpu: AMD EPYC Processor
# BenchmarkListUser-8 280 4283077 ns/op
# PASS
# ok github.com/marmotedu/iam/internal/apiserver/service/v1 1.798s
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
或使用代码生成:
func main() {
cpuOut, _ := os.Create("cpu.out")
defer cpuOut.Close()
pprof.StartCPUProfile(cpuOut)
defer pprof.StopCPUProfile()
memOut, _ := os.Create("mem.out")
defer memOut.Close()
defer pprof.WriteHeapProfile(memOut)
Sum(3, 5)
}
func Sum(a, b int) int {
return a + b
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
go run pprof.go
1
要分析 HTTP Server 性能,或使用 net/http/pprof 包(参考 pprof/pprof.go (opens new window)、iam/genericapiserver.go (opens new window)):
func Register(r *gin.Engine, prefixOptions ...string) {
prefix := getPrefix(prefixOptions...)
prefixRouter := r.Group(prefix)
{
...
prefixRouter.GET("/profile", pprofHandler(pprof.Profile))
...
}
}
func pprofHandler(h http.HandlerFunc) gin.HandlerFunc {
handler := http.HandlerFunc(h)
return func(c *gin.Context) {
handler.ServeHTTP(c.Writer, c.Request)
}
}
// 要开启 HTTP 性能分析,只需注册 HTTP Handler。
// 在启动服务后,访问:http:// x.x.x.x:8080/debug/pprof 查看 profiles 信息。
if s.enableProfiling {
pprof.Register(s.Engine)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 获取性能数据文件
curl http://127.0.0.1:8080/debug/pprof/profile -o cpu.profile
curl http://127.0.0.1:8080/debug/pprof/heap -o mem.profile
1
2
3
2
3
生成采用数据后,可按以下思路分析性能:
- 采样图:矩形面积最大。
- 火焰图:格子最宽。
- go tool pprof:cum% 最大。
# 分析采样图
以 CPU 分析为例。Go 运行时默认以 100 Hz 的频率对 CPU 使用情况采样,即每秒采样 100 次、每 10 毫秒采样一次。每次采样时记录正在运行的函数,并统计其运行时间,生成 CPU 性能数据(cpu.profile)。
先安装 graphviz:
yum -y install graphviz.x86_64
1
生成调用图
go tool pprof -svg cpu.profile > cpu.svg # svg 格式
go tool pprof -pdf cpu.profile > cpu.pdf # pdf 格式
go tool pprof -png cpu.profile > cpu.png # png 格式
1
2
3
2
3
其中有向线段描述了函数的调用关系(A 调用 B,A -> B)。
矩形面积越大,表示累积采样时间越大。其中包含采样数据:
- 函数 / 方法名:包含包名、结构体名、函数名 / 方法名,方便快速定位,例如
fake(*policies)List
表示fake
包,policies
结构体的List
方法。 - 本地采样时间:以及在采样总数中所占的比例。指采样点落在该函数中的总时间(只包含该函数本身、不包含函数体内调用其它函数占用的时间)。该数值较大说明函数本身耗时较大,应集中分析。
- 累积采样时间:以及在采样总数中所占的比例。指采样点落在该函数,以及被它直接或者间接调用的函数中的总时间(整个函数执行耗时)。该数值较大未必是该函数本身有问题,可能是其调用的函数有性能瓶颈。
# 分析火焰图
火焰图可把采样到的堆栈轨迹(Stack Trace)转化为直观图片显示。
使用 pprof 工具打开数据文件,可在浏览器中直观查看数据:
go tool pprof -http="0.0.0.0:8081" v1.test cpu.profile
1
其中数据采样视图:
- Top,类似于 linux top 的形式,从高到低排序。
- Graph,默认弹出来的就是该模式,也就是上一个图的那种带有调用关系的图。
- Flame Graph:pprof 火焰图。
- Peek:类似于 Top 也是从高到底的排序。
- Source:和交互命令式的那种一样,带有源码标注。
- Disassemble:显示所有的总量。
选择火焰图(Flame Graph):格子越宽的函数,就越可能存在性能问题。
- 每列代表一个调用栈,每个格子代表一个函数。
- 纵轴展示了栈的深度,按照调用关系从上到下排列。最下面的格子代表采样时正在占用 CPU 的函数。
- 调用栈在横向会按照字母排序,并且同样的调用栈会做合并。格子的宽度越大,表示该函数越可能是瓶颈。
- 火焰图格子的颜色是随机的暖色调,方便区分各个调用信息。
# 分析数据
交互式查看 CPU 性能数据文件:
- File,二进制可执行文件名称。
- Type,采样文件的类型,例如 cpu、mem 等。
- Time,生成采样文件的时间。
- Duration,程序执行时间。程序在采样时,会自动分配采样任务给多个核心,总采样时间可能会大于总执行时间。
- (pprof),命令行提示,表示当前在 go tool 的 pprof 工具命令行中(还包括 cgo、doc、pprof、trace 等)。
go tool pprof v1.test cpu.profile
# File: v1.test
# Type: cpu
# Time: Aug 17, 2021 at 2:17pm (CST)
# Duration: 56.48s, Total samples = 440ms ( 0.78%)
# Entering interactive mode (type "help" for commands, "o" for options)
# (pprof)
1
2
3
4
5
6
7
2
3
4
5
6
7
常用命令:
cum:表示采样点落在该函数中的,以及被它调用的函数中的总时间(和百分比)。
内存的性能分析与 CPU 相似:Go 运行时系统会记录程序运行期间的所有堆内存分配。在采样的任何时刻,不管堆内存已用字节数是否有增长,只要有字节被分配且数量足够,分析器就会对它进行采样。
优化的目标是减少内存分配次数以及每次分配的内存大小。