golang 性能優化分析工具 pprof (上)


一、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

前提條件

調試分析 golang 程序,要開啟 profile 然后開始采樣數據。
然后安裝:go get github.com/google/pprof ,后面分析會用到。

采樣數據的方式:

  • 第 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 種:

  • 一個是命令行交互分析模式
  • 一個是圖形可視化分析模式

命令行交互分析

A:命令行交互分析
  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后面還可以帶參數,比如: top15

輸出了相同的信息。

字段 說明
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)

B:命令行下直接輸出分析數據

在命令行直接輸出數據,基本命令格式為:

go tool pprof <format> [options] [binary] <source>

輸入命令:go tool pprof -text democpu.pprof ,輸出:

可視化分析

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. web可視化-瀏覽器上查看數據

運行命令:go tool pprof -http=:8080 democpu.pprof

$ go tool pprof -http=:8080 democpu.pprof
Serving web UI on http://localhost:8080

命令運行完成后,會自動在瀏覽器上打開地址: http://localhost:8080/ui/,我們可以在瀏覽器上查看分析數據:

這張圖就是上面用 web 命令生成的圖。

如果你在 web 瀏覽時沒有這么多菜單可供選擇,那么請安裝原生的 pprof 工具:
go get -u github.com/google/pprof ,然后在啟動 go tool pprof -http=:8080 democpu.pprof ,就會出來菜單。

還可以查看火焰圖, http 地址:http://localhost:8080/ui/flamegraph,可直接點擊 VIEW 菜單下的 Flame Graph 選項查看火焰圖。當然還有其他選項可供選擇,比如 Top,Graph 等等選項。你可以根據需要選擇。

C. 火焰圖 Flame Graph

其實上面的 web 可視化已經包含了火焰圖,把火焰圖集成到了 pprof 里。但為了向性能優化專家 Bredan Gregg 致敬,還是來體會一下火焰圖生成過程。

火焰圖 (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

並把 FlameGraph 安裝目錄位置添加進 Path 中。

  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 文檔 。

四、參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM