golang 效能調優分析工具 pprof (上)

九卷發表於2021-03-29

一、golang 程式效能調優

在 golang 程式中,有哪些內容需要除錯優化?

一般常規內容:

  1. cpu:程式對cpu的使用情況 - 使用時長,佔比等
  2. 記憶體:程式對cpu的使用情況 - 使用時長,佔比,記憶體洩露等。如果在往裡分,程式堆、棧使用情況
  3. I/O:IO的使用情況 - 哪個程式IO佔用時間比較長

golang 程式中:

  1. goroutine:go的協程使用情況,呼叫鏈的情況
  2. goroutine leak:goroutine洩露檢查
  3. go dead lock:死鎖的檢測分析
  4. 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 的二次封裝,一般是服務型應用。比如 web server ,它一直執行。這個包對提供的 http 服務進行資料採集分析。

上面的 pprof 開啟後,每隔一段時間就會採集當前程式的堆疊資訊,獲取函式的 cpu、記憶體等使用情況。通過對取樣的資料進行分析,形成一個資料分析報告。

pprof 以 profile.proto 的格式儲存資料,然後根據這個資料可以生成視覺化的分析報告,支援文字形式和圖形形式報告。
profile.proto 裡具體的資料格式是 protocol buffers

那用什麼方法來對資料進行分析,從而形成文字或圖形報告?

用一個命令列工具 go tool pprof

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
}

編譯程式、採集資料、分析程式:

  1. 編譯 demo.go
go build demo.go
  1. 用 pprof 採集資料,命令如下:
./demo.exe --cpuprofile=democpu.pprof  --memprofile=demomem.pprof

說明:我是 win 系統,這個 demo 就是 demo.exe ,linux 下是 demo

  1. 分析資料,命令如下:
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 資料是動態獲取的,如果想要獲取有效的資料,需要保證應用或服務處於較大的負載中,比如正在執行工作中的服務,或者通過其他工具模擬訪問壓力。
否則如果應用處於空閒狀態,比如 http 服務處於空閒狀態,得到的結果可能沒有任何意義。
(後面會遇到這種問題,http 的 web 服務處於空閒狀態,採集顯示的資料為空)

分析資料,基本的模式有 2 種:

  • 一個是命令列互動分析模式
  • 一個是圖形視覺化分析模式

命令列互動分析

  1. 分析上面採集的資料,命令: go tool pprof democpu.pprof

欄位 說明
Type 分析型別,這裡是 cpu
Duration 程式執行的時長

Duration 下面還有一行提示,這是互動模式(通過輸入 help 獲取幫助資訊,輸入 o 獲取選項資訊)。

可以看出,go 的 pprof 操作還有很多其他命令。

  1. 輸入 help 命令,出來很多幫助資訊:

Commands 下有很多命令資訊,text ,top 2個命令解釋相同,輸入這個 2 個看看:

  1. 輸入 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 軟體,

下載對應的平臺安裝包,安裝完成後,把執行檔案 bin 放入 Path 環境變數中,然後在終端輸入 dot -version 命令檢視是否安裝成功。

生成視覺化檔案:

有 2 個步驟,根據上面採集的資料檔案 democpu.pprof 來進行視覺化:

  1. 命令列輸入:go tool pprof democpu.pprof
  2. 輸入 web 命令

在命令列裡輸入 web 命令,就可以生成一個 svg 格式的檔案,用瀏覽器開啟即可檢視 svg 檔案。

執行上面 2 個命令如下圖:

用瀏覽器檢視生成的 svg 圖:

(檔案太大,只擷取了一小部分圖,完整的圖請自行生成檢視)

關於圖形的一點說明:

  1. 每個框代表一個函式,理論上框越大表示佔用的 cpu 資源越多
  2. 每個框之間的線條代表函式之間的呼叫關係,線條上的數字表示函式呼叫的次數
  3. 每個框中第一行數字表示當前函式佔用 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 格式的檔案。

  1. 安裝 go-torch:

go get -v github.com/uber/go-torch

  1. 安裝 flame graph:

git clone https://github.com/brendangregg/FlameGraph.git

  1. 安裝 perl 環境:

生成火焰圖的程式 FlameGraph 是用 perl 寫的,所以先要安裝執行 perl 語言的環境。

  • 安裝 perl 環境:https://www.perl.org/get.html
  • 把執行檔案 bin 加入 Path 中
  • 在終端下執行命令:perl -h ,輸出了幫助資訊,則說明安裝成功

  1. 驗證 FlameGraph 是否安裝成功:

進入到 FlameGraph 安裝目錄,執行命令,./flamegraph.pl --help

輸出資訊說明安裝成功

  1. 生成火焰圖:

重新進入到檔案 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 文件 。

四、參考

相關文章