介绍
问:如何对golang程序进行性能调优?
当然是使用golang中集成的大杀器pprof
,来帮助我们从cpu, heap, block等维度来分析程序,还可以生成调用链、火焰图,直观又好用呀
原理
golang tool中的pprof是基于 https://github.com/google/pprof/ 实现的
pprof性能分析分为两步:数据采样,统计分析
数据采样
runtime/pprof
包中提供了获取各种类型的采样数据的方法。
net/http/pprof
包中提供了http接口调用runtime/pprof
包的采样方法;go test
命令也有参数可调用采样方法生成采样数据统计分析
go tool pprof xxx
, xxx可以是profile文件,也可以是http采样接口
翻译自:golang官方文档-性能分析篇
go runtime包中提供了收集性能分析数据的方法,该方法生成的性能分析数据,能供 pprof 工具使用。性能分析数据可通过
go test
命令生成;也可通过引入net/http/pprof来提供获取性能分析数据的http接口。用户需要先收集性能分析数据,然后通过pprof工具进行分析runtime/pprof 包提供收集以下几种性能分析数据:
cpu: 程序的cpu耗时数据(去除sleeping和I/O等待时间)
heap: 堆内存分配数据,可用于检查内存泄漏
threadcreate: 程序创建thread信息
goroutine:当前所有协程的堆栈跟踪
block: 堵塞的协程信息,默认关闭,要通过runtime.SetBlockProfileRate
打开
mutex: 锁争用信息,默认关闭,要通过`runtime. SetMutexProfileFraction 打开
实践
golang程序要使用pprof很简单,两步解决:
- 引入包
import _ "net/http/pprof"
, 里面提供了如下接口
func init() {
http.HandleFunc("/debug/pprof/", Index) //首页,子路径还包含了/debug,/pprof,/heap
http.HandleFunc("/debug/pprof/cmdline", Cmdline)
http.HandleFunc("/debug/pprof/profile", Profile)
http.HandleFunc("/debug/pprof/symbol", Symbol)
http.HandleFunc("/debug/pprof/trace", Trace)
}
- 如果程序没有提供http服务,开启一个http服务
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
完整的示例代码,借鉴自golang pprof 实战:
import (
"log"
"net/http"
//引入 提供采集数据http接口的包
_ "net/http/pprof"
"os"
"runtime"
"time"
)
const (
Ki = 1024
Mi = Ki * Ki
Gi = Ki * Mi
)
func main() {
runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载
runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪
go func() {
// 开启http服务
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
for {
eatCpu()
stealMemory()
time.Sleep(time.Second)
}
}
func eatCpu() {
loop := 10000000000
for i := 0; i < loop; i++ {
// do nothing
}
}
func stealMemory() {
max := Gi
var buffer [][Mi]byte
// 每次向buffer中 追加 1MB,直到1GB为止
for len(buffer)*Mi < max {
buffer = append(buffer, [Mi]byte{})
}
}
开启http服务后,我们主要有三种方式来分析程序
1. index页查看原始采集数据分析
运行程序,打开pprof index页:http://localhost:6060/debug/pprof/
可在页面直接打开allocs
,profile
,heap
等这些子目录,不过都是些原始采集数据,可读性不高。
类型 | 描述 |
---|---|
allocs | 内存分配情况的采样信息 |
blocks | 阻塞操作情况的采样信息 |
cmdline | 显示程序启动命令及参数 |
goroutine | 当前所有协程的堆栈信息 |
heap | 存活对象内存使用情况的采样信息 |
mutex | 锁争用情况的采样信息 |
profile | CPU 占用情况的采样信息 |
threadcreate | 系统线程创建情况的采样信息 |
trace | 程序运行跟踪信息 |
2. 命令交互分析
下面我们以cpu
维度,使用go tool pprof
来分析下程序的问题
注意 在mac系统下进行cpu分析,可能会出现profile文件为空的情况。建议在linux系统执行
1. 获取采样数据
➜ ~ go tool pprof http://localhost:6060/debug/pprof/profile?second=10
Fetching profile over HTTP from http://192.168.33.111:6060/debug/pprof/profile
Saved profile in /xxxx/pprof/pprof.pprof.samples.cpu.001.pb.gz
File: pprof
Type: cpu
Time: Mar 4, 2020 at 8:49am (CST)
Duration: 30s, Total samples = 21.73s (72.43%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
运行上述命令后,会自动请求http://localhost:6060/debug/pprof/profile?second=10
接口获取profile数据。然后保存在本地/xxxx/pprof/pprof.pprof.samples.cpu.001.pb.gz
,接着自动进入pprof交互界面。
second=10
是指定采集时间,如果不填默认为30s
也可点击index页profile,手动下载数据, 然后go tool pprof 文件路径
进入pprof交互页面
2. 分析
命令行分析主要就是三步大法:top
, list
, web
- top
列出函数的cpu耗时排序,从下图我们可以看出主要是main.eatCpu
这个函数占用了cpu时间片
。也可用命令指定前十条top10
(pprof) top
Showing nodes accounting for 21.73s, 100% of 21.73s total
flat flat% sum% cum cum%
21.73s 100% 100% 21.73s 100% main.eatCpu
0 0% 100% 21.73s 100% main.main
0 0% 100% 21.73s 100% runtime.main
flat:给定函数上运行耗时
flat%:给定函数的CPU 运行耗时比例
sum%:当前行以及上面行flat%加起来的总耗时比例
cum:当前函数加上它之上的调用运行耗时
cum%:当前函数加上它之上的调用的 CPU 运行耗时比例
注意, flat, sum, cum在分析不同维度数据时,根据指定指标,代表含义不同。例如现在分析cpu,则默认flat代表cpu耗时,如果分析heap,则默认flat代表消耗内存,指标可用sample_index命令修改
还不清楚的同学可以看: pprof and golang - how to interpret a results?
- list
搜索查看函数细节,模糊匹配; 可定位到是eatCpu
函数44行代码的for循环有问题。
(pprof) list main.eatCpu
Total: 21.73s
ROUTINE ======================== main.eatCpu in /Users/shuzhengwang/go/src/github.com/Mrdshu/codeDemo/goDemo/pprof/main.go
21.73s 21.73s (flat, cum) 100% of Total
. . 39: }
. . 40:}
. . 41:
. . 42:func eatCpu() {
. . 43: loop := 10000000000
21.73s 21.73s 44: for i := 0; i < loop; i++ {
. . 45: // do nothing
. . 46: }
. . 47:}
. . 48:
. . 49:func stealMemory() {
(pprof)
-
web
查看调用链
如果觉得上面的命令还不够直观,可以直接在pprof交互界面输入web
,查看调用链图像,越大的框耗时越长
sample index
除了上述三个命令外,还有一个重要的命令我们可以用来修改指标
例如在内存分析中,我们查看有四种指标,默认指标是:分配空间大小alloc_space
(pprof) help sample_index
Sample value to report (0-based index or name)
Profiles contain multiple values per sample.
Use sample_index=i to select the ith value (starting at 0).
Or use sample_index=name, with name in [alloc_objects alloc_space inuse_objects inuse_space].
我们修改指标为:分配对象数目alloc_objects
,再用top查看,会发现flat现在展示成了数目而不是内存
(pprof) top
Showing nodes accounting for 1GB, 100% of 1GB total
flat flat% sum% cum cum%
1GB 100% 100% 1GB 100% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal
0 0% 100% 1GB 100% github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Live
0 0% 100% 1GB 100% main.main
0 0% 100% 1GB 100% runtime.main
(pprof)
(pprof) sample_index=alloc_objects
(pprof) top
Showing nodes accounting for 14951, 99.87% of 14970 total
Dropped 14 nodes (cum <= 74)
Showing top 10 nodes out of 18
flat flat% sum% cum cum%
8192 54.72% 54.72% 8192 54.72% syscall.(*RawSockaddrAny).Sockaddr
6554 43.78% 98.50% 6554 43.78% time.NewTimer
205 1.37% 99.87% 205 1.37% github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run
0 0% 99.87% 205 1.37% github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Live
0 0% 99.87% 6554 43.78% github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Live
0 0% 99.87% 6554 43.78% github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Pee
0 0% 99.87% 6770 45.22% main.main
0 0% 99.87% 8192 54.72% main.main.func1
0 0% 99.87% 8192 54.72% net.(*TCPListener).AcceptTCP
0 0% 99.87% 8192 54.72% net.(*TCPListener).accept
3.网页图形化交互
除了命令行交互外,pprof还提供了更直观的网页交互
➜ ~ go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap
Fetching profile over HTTP from http://localhost:6060/debug/pprof/heap
Saved profile in /Users/shuzhengwang/pprof/pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.008.pb.gz
Serving web UI on http://localhost:8081
使用go tool pprof -http=:8081 xxx
命令在8081端口开启网页交互
可以直接在网页上查看函数耗时排序, 查询函数细节,查看调用链,甚至火焰图:
除了cpu耗时分析外,还有其他的分析有兴趣朋友也可以自己试试,和上面一样的流程:
go tool pprof http://localhost:6060/debug/pprof/profile
排查CPU占用问题
go tool pprof http://localhost:6060/debug/pprof/heap
排查内存占用问题
go tool pprof http://localhost:6060/debug/pprof/allocs
排查频繁内存回收
go tool pprof http://localhost:6060/debug/pprof/goroutine
排查协程泄漏
go tool pprof http://localhost:6060/debug/pprof/mutex
排查不合理的锁争用
go tool pprof http://localhost:6060/debug/pprof/block
排查阻塞操作
curl http://localhost:6060/debug/pprof/trace?seconds=60 > trace.out
查看trace信息
生产环境的使用
pprof这么好用,能够在生产上开启吗,采集数据时会对live机器性能有影响不?
官方的说法是,在生产开启是安全的,但部分profile分析(cpu
)会稍微增加系统性能的压力,建议在上生产前,先测试下开启pprof的性能耗损。
cpu profile会增加性能压力的原因在于,其余维度的数据采集速度很快,直接采集当前的堆栈上的信息就可以了。而cpu profile为了分析出函数的cpu占用耗时, 默认采集30s(可改),采集频率硬编码为100hz(即1s采集100次)
我自己测试了一下在cpu 接近99%满负载的情况下,进行60s的cpu profile:
基本看不出什么波动……而根据网上查的资料Continuous Profiling of Go programs,在进行cpu profile时,最多增加5%的额外负载。采集完了就又恢复,所以一般是没啥子问题的啦
扩展——StackImpact
我们利用pprof的采集功能,定时采集,就可以动态的显示出系统的性能图了
StackImpact是一个开源的性能分析器,支持多种语言,我们可以利用它来定时的抓取pprof数据,然后分析系统瓶颈特性
- 用于CPU使用,内存分配,阻塞呼叫的连续热点分析。
- 持续延迟瓶颈跟踪。
- 错误和紧急监视。
- 健康监测包括 CPU。内存。垃圾收集和其他运行时指标。
- 关于配置文件异常的警报。
StackImpact的使用很简单,这里不展开,有兴趣的朋友可以去看看:stackimpact-go