Go pprof性能調優

更新、更全的《Go從入門到放棄》的更新網站,更有python、go、人工智能教學等着你:http://www.javashuo.com/article/p-mxrjjcnn-hn.htmlnode

在計算機性能調試領域裏,profiling 是指對應用程序的畫像,畫像就是應用程序使用 CPU 和內存的狀況。 Go語言是一個對性能特別看重的語言,所以語言中自帶了 profiling 的庫,這篇文章就要講解怎麼在 golang 中作 profiling。python

1、Go性能優化

Go語言項目中的性能優化主要有如下幾個方面:git

  • CPU profile:報告程序的 CPU 使用狀況,按照必定頻率去採集應用程序在 CPU 和寄存器上面的數據
  • Memory Profile(Heap Profile):報告程序的內存使用狀況
  • Block Profiling:報告 goroutines 不在運行狀態的狀況,能夠用來分析和查找死鎖等性能瓶頸
  • Goroutine Profiling:報告 goroutines 的使用狀況,有哪些 goroutine,它們的調用關係是怎樣的

2、採集性能數據

Go語言內置了獲取程序的運行數據的工具,包括如下兩個標準庫:github

  • runtime/pprof:採集工具型應用運行數據進行分析
  • net/http/pprof:採集服務型應用運行時數據進行分析

pprof開啓後,每隔一段時間(10ms)就會收集下當前的堆棧信息,獲取格格函數佔用的CPU以及內存資源;最後經過對這些採樣數據進行分析,造成一個性能分析報告。golang

注意,咱們只應該在性能測試的時候纔在代碼中引入pprof。web

3、工具型應用

若是你的應用程序是運行一段時間就結束退出類型。那麼最好的辦法是在應用退出的時候把 profiling 的報告保存到文件中,進行分析。對於這種狀況,可使用runtime/pprof庫。 首先在代碼中導入runtime/pprof工具:windows

import "runtime/pprof"

3.1 CPU性能分析

開啓CPU性能分析:瀏覽器

pprof.StartCPUProfile(w io.Writer)

中止CPU性能分析:性能優化

pprof.StopCPUProfile()

應用執行結束後,就會生成一個文件,保存了咱們的 CPU profiling 數據。獲得採樣數據以後,使用go tool pprof工具進行CPU性能分析。

3.2 內存性能優化

記錄程序的堆棧信息

pprof.WriteHeapProfile(w io.Writer)

獲得採樣數據以後,使用go tool pprof工具進行內存性能分析。

go tool pprof默認是使用-inuse_space進行統計,還可使用-inuse-objects查看分配對象的數量。

4、服務型應用

若是你的應用程序是一直運行的,好比 web 應用,那麼可使用net/http/pprof庫,它可以在提供 HTTP 服務進行分析。

若是使用了默認的http.DefaultServeMux(一般是代碼直接使用 http.ListenAndServe(「0.0.0.0:8000」, nil)),只須要在你的web server端代碼中按以下方式導入net/http/pprof

import _ "net/http/pprof"

若是你使用自定義的 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)

若是你使用的是gin框架,那麼推薦使用"github.com/DeanThompson/ginpprof"

無論哪一種方式,你的 HTTP 服務都會多出/debug/pprof endpoint,訪問它會獲得相似下面的內容: pprof2.png 這個路徑下還有幾個子頁面:

  • /debug/pprof/profile:訪問這個連接會自動進行 CPU profiling,持續 30s,並生成一個文件供下載
  • /debug/pprof/heap: Memory Profiling 的路徑,訪問這個連接會獲得一個內存 Profiling 結果的文件
  • /debug/pprof/block:block Profiling 的路徑
  • /debug/pprof/goroutines:運行的 goroutines 列表,以及調用關係

5、go tool pprof命令

不論是工具型應用仍是服務型應用,咱們使用相應的pprof庫獲取數據以後,下一步的都要對這些數據進行分析,咱們可使用go tool pprof命令行工具。

go tool pprof最簡單的使用方式爲:

go tool pprof [binary] [source]

其中:

  • binary 是應用的二進制文件,用來解析各類符號;
  • source 表示 profile 數據的來源,能夠是本地的文件,也能夠是 http 地址。

注意事項: 獲取的 Profiling 數據是動態的,要想得到有效的數據,請保證應用處於較大的負載(好比正在生成中運行的服務,或者經過其餘工具模擬訪問壓力)。不然若是應用處於空閒狀態,獲得的結果可能沒有任何意義。

6、具體示例

首先咱們來寫一段有問題的代碼:

// runtime_pprof/main.go
package main

import (
    "flag"
    "fmt"
    "os"
    "runtime/pprof"
    "time"
)

// 一段有問題的代碼
func logicCode() {
    var c chan int
    for {
        select {
        case v := <-c:
            fmt.Printf("recv from chan, value:%v\n", v)
        default:

        }
    }
}

func main() {
    var isCPUPprof bool
    var isMemPprof bool

    flag.BoolVar(&isCPUPprof, "cpu", false, "turn cpu pprof on")
    flag.BoolVar(&isMemPprof, "mem", false, "turn mem pprof on")
    flag.Parse()

    if isCPUPprof {
        file, err := os.Create("./cpu.pprof")
        if err != nil {
            fmt.Printf("create cpu pprof failed, err:%v\n", err)
            return
        }
        pprof.StartCPUProfile(file)
        defer pprof.StopCPUProfile()
    }
    for i := 0; i < 8; i++ {
        go logicCode()
    }
    time.Sleep(20 * time.Second)
    if isMemPprof {
        file, err := os.Create("./mem.pprof")
        if err != nil {
            fmt.Printf("create mem pprof failed, err:%v\n", err)
            return
        }
        pprof.WriteHeapProfile(file)
        file.Close()
    }
}

經過flag咱們能夠在命令行控制是否開啓CPU和Mem的性能分析。 將上面的代碼保存並編譯成runtime_pprof可執行文件,執行時加上-cpu命令行參數以下:

./runtime_pprof -cpu

等待30秒後會在當前目錄下生成一個cpu.pprof文件。

6.1 命令行交互界面

咱們使用go工具鏈裏的pprof來分析一下。

go tool pprof cpu.pprof

執行上面的代碼會進入交互界面以下:

runtime_pprof $ go tool pprof cpu.pprof
Type: cpu
Time: Jun 28, 2019 at 11:28am (CST)
Duration: 20.13s, Total samples = 1.91mins (568.60%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

咱們能夠在交互界面輸入top3來查看程序中佔用CPU前3位的函數:

(pprof) top3
Showing nodes accounting for 100.37s, 87.68% of 114.47s total
Dropped 17 nodes (cum <= 0.57s)
Showing top 3 nodes out of 4
      flat  flat%   sum%        cum   cum%
    42.52s 37.15% 37.15%     91.73s 80.13%  runtime.selectnbrecv
    35.21s 30.76% 67.90%     39.49s 34.50%  runtime.chanrecv
    22.64s 19.78% 87.68%    114.37s 99.91%  main.logicCode

其中:

  • flat:當前函數佔用CPU的耗時
  • flat::當前函數佔用CPU的耗時百分比
  • sun%:函數佔用CPU的耗時累計百分比
  • cum:當前函數加上調用當前函數的函數佔用CPU的總耗時
  • cum%:當前函數加上調用當前函數的函數佔用CPU的總耗時百分比
  • 最後一列:函數名稱

在大多數的狀況下,咱們能夠經過分析這五列得出一個應用程序的運行狀況,並對程序進行優化。

咱們還可使用list 函數名命令查看具體的函數分析,例如執行list logicCode查看咱們編寫的函數的詳細分析。

(pprof) list logicCode
Total: 1.91mins
ROUTINE ================ main.logicCode in .../runtime_pprof/main.go
    22.64s   1.91mins (flat, cum) 99.91% of Total
         .          .     12:func logicCode() {
         .          .     13:   var c chan int
         .          .     14:   for {
         .          .     15:           select {
         .          .     16:           case v := <-c:
    22.64s   1.91mins     17:                   fmt.Printf("recv from chan, value:%v\n", v)
         .          .     18:           default:
         .          .     19:
         .          .     20:           }
         .          .     21:   }
         .          .     22:}

經過分析發現大部分CPU資源被17行佔用,咱們分析出select語句中的default沒有內容會致使上面的case v:=<-c:一直執行。咱們在default分支添加一行time.Sleep(time.Second)便可。

6.2 圖形化

或者能夠直接輸入web,經過svg圖的方式查看程序中詳細的CPU佔用狀況。 想要查看圖形化的界面首先須要安裝graphviz圖形化工具。

Mac:

brew install graphviz

Windows: 下載graphvizgraphviz安裝目錄下的bin文件夾添加到Path環境變量中。 在終端輸入dot -version查看是否安裝成功。

cpu_pprof.png 關於圖形的說明: 每一個框表明一個函數,理論上框的越大表示佔用的CPU資源越多。 方框之間的線條表明函數之間的調用關係。 線條上的數字表示函數調用的次數。 方框中的第一行數字表示當前函數佔用CPU的百分比,第二行數字表示當前函數累計佔用CPU的百分比。

7、go-torch和火焰圖

火焰圖(Flame Graph)是 Bredan Gregg 建立的一種性能分析圖表,由於它的樣子近似 🔥而得名。上面的 profiling 結果也轉換成火焰圖,若是對火焰圖比較瞭解能夠手動來操做,不過這裏咱們要介紹一個工具:go-torch。這是 uber 開源的一個工具,能夠直接讀取 golang profiling 數據,並生成一個火焰圖的 svg 文件。

7.1 安裝go-touch

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

火焰圖 svg 文件能夠經過瀏覽器打開,它對於調用圖的最優勢是它是動態的:能夠經過點擊每一個方塊來 zoom in 分析它上面的內容。

火焰圖的調用順序從下到上,每一個方塊表明一個函數,它上面一層表示這個函數會調用哪些函數,方塊的大小表明了佔用 CPU 使用的長短。火焰圖的配色並無特殊的意義,默認的紅、黃配色是爲了更像火焰而已。

go-torch 工具的使用很是簡單,沒有任何參數的話,它會嘗試從http://localhost:8080/debug/pprof/profile獲取 profiling 數據。它有三個經常使用的參數能夠調整:

  • -u –url:要訪問的 URL,這裏只是主機和端口部分
  • -s –suffix:pprof profile 的路徑,默認爲 /debug/pprof/profile
  • –seconds:要執行 profiling 的時間長度,默認爲 30s

7.2 安裝 FlameGraph

要生成火焰圖,須要事先安裝 FlameGraph工具,這個工具的安裝很簡單(須要perl環境支持),只要把對應的可執行文件加入到環境變量中便可。

  1. 下載安裝perl:https://www.perl.org/get.html
  2. 下載FlameGraph:git clone https://github.com/brendangregg/FlameGraph.git
  3. FlameGraph目錄加入到操做系統的環境變量中。
  4. Windows平臺的同窗,須要把go-torch/render/flamegraph.go文件中的GenerateFlameGraph按以下方式修改,而後在go-torch目錄下執行go install便可。
// GenerateFlameGraph runs the flamegraph script to generate a flame graph SVG. func GenerateFlameGraph(graphInput []byte, args ...string) ([]byte, error) {
flameGraph := findInPath(flameGraphScripts)
if flameGraph == &quot;&quot; {
    return nil, errNoPerlScript
}
if runtime.GOOS == &quot;windows&quot; {
    return runScript(&quot;perl&quot;, append([]string{flameGraph}, args...), graphInput)
}
  return runScript(flameGraph, args, graphInput)
}

7.3 壓測工具wrk

推薦使用https://github.com/wg/wrkhttps://github.com/adjust/go-wrk

7.4 使用go-torch

使用wrk進行壓測:go-wrk -n 50000 http://127.0.0.1:8080/book/list 在上面壓測進行的同時,打開另外一個終端執行go-torch -u http://127.0.0.1:8080 -t 30,30秒以後終端會初夏以下提示:Writing svg to torch.svg

而後咱們使用瀏覽器打開torch.svg就能看到以下火焰圖了。 pprof3.png 火焰圖的y軸表示cpu調用方法的前後,x軸表示在每一個採樣調用時間內,方法所佔的時間百分比,越寬表明佔據cpu時間越多。經過火焰圖咱們就能夠更清楚的找出耗時長的函數調用,而後不斷的修正代碼,從新採樣,不斷優化。

8、pprof與性能測試結合

go test命令有兩個參數和 pprof 相關,它們分別指定生成的 CPU 和 Memory profiling 保存的文件:

  • -cpuprofile:cpu profiling 數據要保存的文件地址
  • -memprofile:memory profiling 數據要報文的文件地址

咱們還能夠選擇將pprof與性能測試相結合,好比:

好比下面執行測試的同時,也會執行 CPU profiling,並把結果保存在 cpu.prof 文件中:

go test -bench . -cpuprofile=cpu.prof

好比下面執行測試的同時,也會執行 Mem profiling,並把結果保存在 cpu.prof 文件中:

go test -bench . -memprofile=./mem.prof

須要注意的是,Profiling 通常和性能測試一塊兒使用,這個緣由在前文也提到過,只有應用在負載高的狀況下 Profiling 纔有意義。

9、練習題

  1. 使用gin框架編寫一個接口,使用go-wrk進行壓測,使用性能調優工具採集數據繪製出調用圖和火焰圖。
相關文章
相關標籤/搜索