一、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 - https://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。
火焰圖形狀如下:
(來自:https://github.com/brendangregg/FlameGraph)
上面用 pprof 生成的取樣資料,要把它轉換成火焰圖,就要使用一個轉換工具 go-torch,這個工具是 uber 開源,它是用 go 語言編寫的,可以直接讀取 pprof 採集的資料,並生成一張火焰圖, svg 格式的檔案。
- 安裝 go-torch:
go get -v github.com/uber/go-torch
- 安裝 flame graph:
- 安裝 perl 環境:
生成火焰圖的程式 FlameGraph 是用 perl 寫的,所以先要安裝執行 perl 語言的環境。
- 安裝 perl 環境:https://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 文件 。