性能分析
- 從業務目標角度,通常我們的瓶頸出現在業務的通量和時延兩個問題上
- 比如一個MySQL數據庫,你從其他機器上對它發請求,每秒它能處理10萬個請求,這個就是通量性能;每個請求的反應時間是0.5ms,這個就是時延性能
- 通量和時延是相互相承的兩個量,當通量達到系統上限,時延就會大幅提高
- 一般而言,性能分析主要關注 CPU、內存、磁盤 IO、網絡這些指標
pprof
三種採樣方式
runtime/pprof
-
適用於程序只運行一次的,例如每天只跑一次的離線預處理程序
-
採樣
- 運行程序後生成
cpu.prof
和mem.prof
兩個文件
- 運行程序後生成
import "runtime/pprof"
func main() {
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
ff, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(ff)
// 待測試的程序
}
net/http/pprof
- 適用於程序長期運行的線上服務
- 底層也是調用的
runtime/pprof
提供的函數,封裝成接口對外提供網絡訪問
- 底層也是調用的
- 如果是使用了HTTP包的路由,則只需要
import _ "net/http/pprof"
即可,會自動註冊路由 - 如果使用了第三方路由(例如gin),則需要手動註冊路由
- gin還可以直接使用
github.com/DeanThompson/ginpprof
包 - 只需要
ginpprof.Wrap(router)
即可
- gin還可以直接使用
import _ "net/http/pprof"
// 如果不是使用默認的 http.DefaultServeMux而是自定義的MUX,則需要手動註冊路由
r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)
benchmark pprof
- 命令行
- 直接運行
go test -bench <bench_func> -run none -cpuprofile <cpu.prof>
- 除了
-cpuprofile
, 還可-memprofile
等
- 直接運行
- Goland
報告查看
# 命令行交互模式
go tool pprof http://<address:port>/debug/pprof/<profile>
# cpu profile默認採樣30s,可以自行修改
go tool pprof http://<address:port>/debug/pprof/<profile>?seconds=120
# 在瀏覽器彙總交互
go tool pprof --http=:<port> http://<address:port>/debug/pprof/<profile>
```
<profile>
包括profile(CPU), heap(內存),goroutine,block,mutex
命令行
-
總的思路就是通過
top
和web
找出關鍵函數,再通過list Func
查看函數代碼,找到關鍵代碼行並確認優化方案 -
top <num>
查看佔用最高的num項
-
list <func name>
查看匹配的具體函數CPU和內存等佔用數據- 支持正則匹配
- 如果函數名不明確,會進行模糊匹配
- 使用list命令的前提是程序的源碼在當前機器
-
traces
打印所有調用棧,以及調用棧的指標信息- 每個
- - - - -
隔開的是一個調用棧
- 每個
瀏覽器
- 需要先安裝
graphviz
工具
指標
CPU
- CPU 性能分析啓用後,Go runtime 會每 10ms 就暫停一下,記錄當前運行的 goroutine 的調用堆棧及相關數據
內存
-
內存性能分析則是在一段時間內對在堆分配進行採樣
- 棧分配由於會隨時釋放,因此不會被內存分析所記錄
- 內存profiling是基於抽樣和它跟蹤的是已分配的內存,而不是使用中的內存
- 比如有些內存已經分配,但實際以及不使用的內存,比如內存泄露的那部分
- 所以不能使用內存profiling衡量程序總體的內存使用情況
-
有兩種內存分析策略:
- 一種是當前的(這一次採集)內存或對象的分配,稱爲
inuse
- 另一種是從程序運行到現在所有的內存分配,不管是否已經被 gc 過了,稱爲
alloc
- 加上
-sample_index
參數後,可以切換內存分析的類型 - 例如
go tool pprof -sample_index=alloc_space http://<address:port>/debug/pprof/heap
- 一種是當前的(這一次採集)內存或對象的分配,稱爲
-inuse_space 未釋放空間數
-inuse_objects 未釋放對象數
-alloc_space 累計分配空間數
-alloc_objects 累計分配對象數
goroutine
goroutine profile: total 32023
32015 @ 0x42e15a 0x42e20e 0x40534b 0x4050e5 0x6d8559 0x6d831b 0x45abe1
# 0x6d8558 main.alloc2.func1+0xf8 /home/ubuntu/heap/leak_demo.go:53
# 0x6d831a main.alloc2+0x2a /home/ubuntu/heap/leak_demo.go:54
goroutine?debug=1
goroutine profile: total 32023
- 32023是goroutine的總數量
32015 @ 0x42e15a 0x42e20e 0x40534b 0x4050e5 ...
- 32015代表當前有32015個goroutine運行這個調用棧,並且停在相同位置
# 0x6d8558 main.alloc2.func1+0xf8 /home/ubuntu/heap/leak_demo.go:53
- goroutine的調用棧,列出了函數和所在文件的行數
goroutine 20 [chan send, 2 minutes]:
main.alloc2.func1(0xc42015e060)
/home/ubuntu/heap/leak_demo.go:53 +0xf9
main.alloc2(0xc42015e060)
/home/ubuntu/heap/leak_demo.go:54 +0x2b
created by main.alloc1
/home/ubuntu/heap/leak_demo.go:42 +0x3f
goroutine?debug=2
- 和第1種方式是互補的,可以看到每個goroutine的信息
goroutine 20 [chan send, 2 minutes]
- 20是goroutine的id,
[]
中是當前goroutine的狀態: 阻塞在寫channel,並且阻塞了2分鐘
火焰圖
- y軸表示調用方法的先後
- x軸表示在每個採樣調用時間內,方法所佔的時間/空間百分比
- 越寬代表佔據cpu時間/mem大小越多
參考
- https://segmentfault.com/a/1190000019222661
- https://juejin.im/entry/5ac9cf3a518825556534c76e
- http://xiaorui.cc/archives/5577
- https://zhuanlan.zhihu.com/p/91241270