golang 性能調優分析工具 pprof (上)
一、golang 程序性能調優
在 golang 程序中,有哪些內容需要調試優化?
一般常規內容:
- cpu:程序對cpu的使用情況 – 使用時長,佔比等
- 內存:程序對cpu的使用情況 – 使用時長,佔比,內存泄露等。如果在往裡分,程序堆、棧使用情況
- I/O:IO的使用情況 – 哪個程序IO佔用時間比較長
golang 程序中:
- goroutine:go的協程使用情況,調用鏈的情況
- goroutine leak:goroutine泄露檢查
- go dead lock:死鎖的檢測分析
- data race detector:數據競爭分析,其實也與死鎖分析有關
上面是在 golang 程序中,性能調優的一些內容。
有什麼方法工具調試優化 golang 程序?
比如 linux 中 cpu 性能調試,工具有 top,dstat,perf 等。
那麼在 golang 中,有哪些分析方法?
golang 性能調試優化方法:
- Benchmark:基準測試,對特定代碼的運行時間和內存信息等進行測試
- Profiling:程序分析,程序的運行畫像,在程序執行期間,通過採樣收集的數據對程序進行分析
- Trace:跟蹤,在程序執行期間,通過採集發生的事件數據對程序進行分析
profiling 和 trace 有啥區別?
profiling 分析沒有時間線,trace 分析有時間線。
在 golang 中,應用方法的工具呢?
這裡介紹 pprof 這個 golang 工具,它可以幫助我們調試優化程序。
它的最原始程序是 gperftools – //github.com/gperftools/gperftools,golang 的 pprof 是從它而來的。
二、pprof 介紹
簡介
pprof 是 golang 官方提供的性能調優分析工具,可以對程序進行性能分析,並可視化數據,看起來相當的直觀。
當你的 go 程序遇到性能瓶頸時,可以使用這個工具來進行調試並優化程序。
本文將對下面 golang 中 2 個監控性能的包 pprof 進行運用:
- runtime/pprof:採集程序運行數據進行性能分析,一般用於後台工具型應用。
- net/http/pprof:對 runtime/pprof 的二次封裝,一般應用於 http server ,採集 http server 運行時數據進行分析。
pprof 開啟後,每隔一段時間就會採集當前程序的堆棧信息,獲取函數的 cpu、內存等使用情況。通過對採樣的數據進行分析,形成一個數據分析報告。
pprof 以 profile.proto 的格式保存數據,然後根據這個數據生成可視化的分析報告,支持文本形式和圖形形式報告。
profile.proto 里具體的數據格式是 protocol buffers。
pprof 使用模式
-
Report generation:報告生成
-
Interactive terminal use:交互式終端
-
Web interface:Web 界面
三、runtime/pprof
前提條件
先安裝 pprof: go get -u github.com/google/pprof
。
調試分析 golang 程序,要開啟 profile 然後開始採樣數據。
採樣數據的方式:
- 第 1 種,在 go 程序中添加如下代碼:
StartCPUProfile 為當前 process 開啟 CPU profiling 。
StopCPUProfile 停止當前的 CPU profile。當所有的 profile 寫完了後它才返回。
// 開啟 cpu 採集分析:
pprof.StartCPUProfile(w io.Writer)
// 停止 cpu 採集分析:
pprof.StopCPUProfile()
WriteHeapProfile 把內存 heap 相關的內容寫入到文件中
pprof.WriteHeapProfile(w io.Writer)
- 第 2 種,在 benchmark 測試的時候
go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
- 還有一種,對 http server 採集數據
go tool pprof $host/debug/pprof/profile
程序示例
go version go1.13.9
例子 1
我們用第 1 種方法,在程序中添加分析代碼,demo.go :
package main
import (
"bytes"
"flag"
"log"
"math/rand"
"os"
"runtime"
"runtime/pprof"
"sync"
)
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
var memprofile = flag.String("memprofile", "", "write mem 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()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
var wg sync.WaitGroup
wg.Add(200)
for i := 0; i < 200; i++ {
go cyclenum(30000, &wg)
}
writeBytes()
wg.Wait()
if *memprofile != "" {
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f.Close()
runtime.GC()
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("cound not write memory profile: ", err)
}
}
}
func cyclenum(num int, wg *sync.WaitGroup) {
slice := make([]int, 0)
for i := 0; i < num; i++ {
for j := 0; j < num; j++ {
j = i + j
slice = append(slice, j)
}
}
wg.Done()
}
func writeBytes() *bytes.Buffer {
var buff bytes.Buffer
for i := 0; i < 30000; i++ {
buff.Write([]byte{'0' + byte(rand.Intn(10))})
}
return &buff
}
編譯程序、採集數據、分析程序:
- 編譯 demo.go
go build demo.go
- 用 pprof 採集數據,命令如下:
./demo.exe --cpuprofile=democpu.pprof --memprofile=demomem.pprof
說明:我是 win 系統,這個 demo 就是 demo.exe ,linux 下是 demo
- 分析數據,命令如下:
go tool pprof democpu.pprof
go tool pprof 簡單的使用格式為:go tool pprof [binary] [source]
- binary: 是應用的二進制文件,用來解析各種符號
- source: 表示 profile 數據的來源,可以是本地的文件,也可以是 http 地址
要了解 go tool pprof 更多命令使用方法,請查看文檔:
go tool pprof --help
注意:
獲取的 Profiling 數據是動態的,要想獲得有效的數據,請保證應用處於較大的負載(比如正在生成中運行的服務,或者通過其他工具模擬訪問壓力)。否則如果應用處於空閑狀態,得到的結果可能沒有任何意義。
(後面會遇到這種問題)
分析數據,基本的模式有 2 種:
- 一個是命令行交互分析模式
- 一個是圖形可視化分析模式
命令行交互分析
- 分析上面採集的數據,命令:
go tool pprof democpu.pprof
字段 | 說明 |
---|---|
Type: | 分析類型,這裡是 cpu |
Duration: | 程序執行的時長 |
Duration 下面還有一行提示,這是交互模式(通過輸入 help 獲取幫助信息,輸入 o 獲取選項信息)。
可以看出,go 的 pprof 操作還有很多其他命令。
- 輸入 help 命令,出來很多幫助信息:
Commands 下有很多命令信息,text ,top 2個命令解釋相同,輸入這個 2 個看看:
- 輸入 top,text 命令
top 命令:對函數的 cpu 耗時和百分比排序後輸出
top後面還可以帶參數,比如: top 15
輸出了相同的信息。
字段 | 說明 |
---|---|
flat | 當前函數佔用 cpu 耗時 |
flat % | 當前函數佔用 cpu 耗時百分比 |
sum% | 函數佔用 cpu 時間累積佔比,從小到大一直累積到 100% |
cum | 當前函數加上調用當前函數的函數佔用 cpu 的總耗時 |
%cum | 當前函數加上調用當前函數的函數佔用 cpu 的總耗時佔比 |
從字段數據我們可以看出哪一個函數比較耗費時間,就可以對這個函數進一步分析。
分析用到的命令是 list
。
list 命令:可以列出函數最耗時的代碼部分,格式:list 函數名
從上面採樣數據可以分析出總耗時最長的函數是 main.cycylenum
,用 list cyclenum
命令進行分析,如下圖:
發現最耗時的代碼是 62 行:slice = append(slice, j)
,這裡耗時有 1.47s ,可以對這個地方進行優化。
這裡耗時的原因,應該是 slice 的實時擴容引起的。那我們空間換時間,固定 slice 的容量,make([]int, num * num)
可視化分析
A. pprof 圖形可視化
除了上面的命令行交互分析,還可以用圖形化來分析程序性能。
圖形化分析前,先要安裝 graphviz 軟件,
- 下載地址:graphviz地址,
下載對應的平台安裝包,安裝完成後,把執行文件 bin 放入 Path 環境變量中,然後在終端輸入 dot -version
命令查看是否安裝成功。
生成可視化文件:
有 2 個步驟,根據上面採集的數據文件 democpu.pprof 來進行可視化:
- 命令行輸入:go tool pprof democpu.pprof
- 輸入 web 命令
在命令行里輸入 web 命令,就可以生成一個 svg 格式的文件,用瀏覽器打開即可查看 svg 文件。
執行上面 2 個命令如下圖:
用瀏覽器查看生成的 svg 圖:
(文件太大,只截取了一小部分圖,完整的圖請自行生成查看)
關於圖形的一點說明:
- 每個框代表一個函數,理論上框越大表示佔用的 cpu 資源越多
- 每個框之間的線條代表函數之間的調用關係,線條上的數字表示函數調用的次數
- 每個框中第一行數字表示當前函數佔用 cpu 的百分比,第二行數字表示當前函數累計佔用 cpu 的百分比
B. 火焰圖 Flame Graph
火焰圖 (Flame Graph) 是性能優化專家 Bredan Gregg 創建的一種性能分析圖。Flame Graphs visualize profiled code。
火焰圖形狀如下:
(來自://github.com/brendangregg/FlameGraph)
上面用 pprof 生成的採樣數據,要把它轉換成火焰圖,就要使用一個轉換工具 go-torch,這個工具是 uber 開源,它是用 go 語言編寫的,可以直接讀取 pprof 採集的數據,並生成一張火焰圖, svg 格式的文件。
- 安裝 go-torch:
go get -v github.com/uber/go-torch
- 安裝 flame graph:
git clone //github.com/brendangregg/FlameGraph.git
- 安裝 perl 環境:
生成火焰圖的程序 FlameGraph 是用 perl 寫的,所以先要安裝執行 perl 語言的環境。
- 安裝 perl 環境://www.perl.org/get.html
- 把執行文件 bin 加入 Path 中
- 在終端下執行命令:
perl -h
,輸出了幫助信息,則說明安裝成功
- 驗證 FlameGraph 是否安裝成功:
進入到 FlameGraph 安裝目錄,執行命令,./flamegraph.pl --help
輸出信息說明安裝成功
- 生成火焰圖:
重新進入到文件 democpu.pprof 的目錄,然後執行命令:
go-torch -b democpu.pprof
上面命令默認生成名為 torch.svg 的文件,用瀏覽器打開查看:
自定義輸出文件名,後面加 -f
參數:
go-torch -b democpu.pprof -f cpu_flamegraph.svg
火焰圖說明:
火焰圖 svg 文件,你可以點擊上面的每個方塊來查看分析它上面的內容。
火焰圖的調用順序從下到上,每個方塊代表一個函數,它上面一層表示這個函數會調用哪些函數,方塊的大小代表了佔用 CPU 使用時長長短。
go-torch 的命令格式:
go-torch [options] [binary] <profile source>
go-torch 幫助文檔:
想了解更多 go-torch 用法,請用 help 命令查看幫助文檔,
go-torch --help
。或查看 go-torch README 文檔 。