本文会详细讲述如何通过pprof分析go程序的性能,让你看懂自己的程序都是在哪消耗的性能。本文从下面四个方面去讲述:

1、 如何在程序中开启pprof:web服务和非web服务

2、 通过pprof分析CPU使用以及如何看懂他们的输出:分析各个函数执行时间的长度,让你知道代码是怎么写挫的

3、 通过pprof分析内存的分配:看看你的代码哪里申请的内存不合理,如何优化

4、 讲述一些pprof的边缘使用:主要是指引一下,后续留给你去探索


本文就不对pprof的命令(本文就用了top${n},web, list)进行详细讲解,因为通过help命令看一下就知道了,就那么几个。

注:文中的代码在

https://github.com/helios741/benchgraffiti/tree/master/havlak

在程序中使用pprof

如果是非web程序开启pprof的话使得用的runtime/pprof包,开启方式如下:

var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
var memprofile = flag.String("memprofile", "", "write memory profile to `file`")


func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close()
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}




if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f.Close()
pprof.WriteHeapProfile(f)
}
...
}


比如你的代码通过go build产生出的二进制叫做main,那么./main -cpuprofile=cpu.prof -memprofile=mem.prof 产出pprof能分析的文件即可。

如果是web程序的话,使用的是net/http/pprof包,比较简单,只需下面代码即可:

import _ "net/http/pprof"
go func() {
log.Println(http.ListenAndServe(":6060", nil))
}()
能够通过下面的命令分析内存使用和cpu使用:
1、 内存使用:
go tool pprof http://localhost:6060/debug/pprof/heap
2、 cpu使用
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

分析程序CPU使用

我们这里以非web方式来做个例子(go tool pprof cpu.prof)。
 分析一下程序top10使用的CPU:

图一,CPU使用的top10

第一行是输入的命令
第二行占用CPU前十名的一共消耗了12060ms,该程序一共耗时是21540ms,前十名占用55.99%。
第三行一共109个现在展示出了10个
第四行第一列表示这个函数的执行时间,第二列表这个时间对总时间的占比,第三列表示前n行总共的时间占比(比如第四行就是11.14 + 9.15 + 7.29 + 7.24 = 34.82),第四列cum(cumulative)和第五列表示自己的执行时间加调用函数的时间以及对应的占比,这个下面会解释一下。第六列表示对应的函数。

图二,CPU累计的前十名

top10 -cum的意思是按照cum这一列进行排序。main.FindLoops的本身只是执行了9.45s - 9.06s = 0.39s。
我们在通过svg的图来看一下,cpu执行的比(需要安装graphviz),执行:

图三,pprof svc展示图

我们能只看一个分支,比如说只想看DFS函数,可以执行

(pprof) web DFS

图四、DFS函数svg图

看到mapaccess应该就看懂,肯定是DFS函数中使用骚操作的map。我们通过下面的命令看一下DFS的代码:

(pprof) list DFS

图五、DFS代码

输出的前三列分别表示该行代码运行的时长该行代码加上以前运行的时间、以及代码行数

241, 242, 246, and 250这四行使用了map。map的key是对象引用起来会变得特别慢。我们改为map[int]int再来看一下。

图六、优化后的DFS代码

分析程序内存使用

我们这里以非web方式来做个例子(go tool pprof mem.prof)。

看一下内存消耗资源的前五名:

图七、memory耗时前五名

明显看到FindLoops是个大头,看一下代码:

图八、FindLoops代码

我们知道golang对于大内存会分配在堆上,堆有两个坏处,一个是慢二是会被GC。所以这肯定不好。

图九、FindLoops大量申请内存部分代码

将这段代码进行优化(改为slice)后:

降低了很多。

本来以为优化了上面之后gc时间也会大大减少呢,结果并没有那么明显,从23.27% -> 23.05%。也可能是我的go版本过于新(1.14)了,在gc上的优化已经够好了,以后遇到gc时间过长(40%以上)也可以这么分析。



分析其他性能问题

本文主要分析了内存的cpu,其实还有很多的内容可以分析。比如分析你如果在程序中调用runtime.SetBlockProfileRate可以观察goroutine的性能。具体的可以参考https://pkg.go.dev/net/http/pprof。

对于调试web服务还是有很多优势的,比如你要远程看你线上服务的性能,的可以这样(其他指标类似):

go tool pprof -http=:6060 http://172.26.186.76:6060/debug/pprof/profile

过一会在你本地就会自动打开浏览器,就会显示下面的图:

我们一般会看火焰图,点击左上角的VIEW就能看到,

随着一张炫酷的图,这篇文章也结束了。


参考:

https://blog.golang.org/pprof



欢迎长按下面的二维码关注云影原生公众号