主頁 > 後端開發 > Go pprof性能調優

Go pprof性能調優

2020-09-15 09:01:24 後端開發

在計算機性能除錯領域里,profiling 是指對應用程式的畫像,畫像就是應用程式使用 CPU 和記憶體的情況, Go語言是一個對性能特別看重的語言,因此語言中自帶了 profiling 的庫,這篇文章就要講解怎么在 golang 中做 profiling,

Go性能優化

Go語言專案中的性能優化主要有以下幾個方面:

  • CPU profile:報告程式的 CPU 使用情況,按照一定頻率去采集應用程式在 CPU 和暫存器上面的資料
  • Memory Profile(Heap Profile):報告程式的記憶體使用情況
  • Block Profiling:報告 goroutines 不在運行狀態的情況,可以用來分析和查找死鎖等性能瓶頸
  • Goroutine Profiling:報告 goroutines 的使用情況,有哪些 goroutine,它們的呼叫關系是怎樣的

采集性能資料

Go語言內置了獲取程式的運行資料的工具,包括以下兩個標準庫:

  • runtime/pprof:采集工具型應用運行資料進行分析
  • net/http/pprof:采集服務型應用運行時資料進行分析

pprof開啟后,每隔一段時間(10ms)就會收集下當前的堆疊資訊,獲取格格函式占用的CPU以及記憶體資源;最后通過對這些采樣資料進行分析,形成一個性能分析報告,

注意,我們只應該在性能測驗的時候才在代碼中引入pprof,

工具型應用

如果你的應用程式是運行一段時間就結束退出型別,那么最好的辦法是在應用退出的時候把 profiling 的報告保存到檔案中,進行分析,對于這種情況,可以使用runtime/pprof庫, 首先在代碼中匯入runtime/pprof工具:

import "runtime/pprof"

CPU性能分析

開啟CPU性能分析:

pprof.StartCPUProfile(w io.Writer)

停止CPU性能分析:

pprof.StopCPUProfile()

應用執行結束后,就會生成一個檔案,保存了我們的 CPU profiling 資料,得到采樣資料之后,使用go tool pprof工具進行CPU性能分析,

記憶體性能優化

記錄程式的堆疊資訊

pprof.WriteHeapProfile(w io.Writer)

得到采樣資料之后,使用go tool pprof工具進行記憶體性能分析,

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

服務型應用

如果你的應用程式是一直運行的,比如 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,訪問它會得到類似下面的內容: 

這個路徑下還有幾個子頁面:

  • /debug/pprof/profile:訪問這個鏈接會自動進行 CPU profiling,持續 30s,并生成一個檔案供下載
  • /debug/pprof/heap: Memory Profiling 的路徑,訪問這個鏈接會得到一個記憶體 Profiling 結果的檔案
  • /debug/pprof/block:block Profiling 的路徑
  • /debug/pprof/goroutines:運行的 goroutines 串列,以及呼叫關系

go tool pprof命令

不管是工具型應用還是服務型應用,我們使用相應的pprof庫獲取資料之后,下一步的都要對這些資料進行分析,我們可以使用go tool pprof命令列工具,

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

go tool pprof [binary] [source]

其中:

  • binary 是應用的二進制檔案,用來決議各種符號;
  • source 表示 profile 資料的來源,可以是本地的檔案,也可以是 http 地址,

注意事項: 獲取的 Profiling 資料是動態的,要想獲得有效的資料,請保證應用處于較大的負載(比如正在生成中運行的服務,或者通過其他工具模擬訪問壓力),否則如果應用處于空閑狀態,得到的結果可能沒有任何意義,

具體示例

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

// 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檔案,

命令列互動界面

我們使用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)即可,

圖形化

或者可以直接輸入web,通過svg圖的方式查看程式中詳細的CPU占用情況, 想要查看圖形化的界面首先需要安裝graphviz圖形化工具,

Mac:

brew install graphviz

Windows: 下載graphviz 將graphviz安裝目錄下的bin檔案夾添加到Path環境變數中, 在終端輸入dot -version查看是否安裝成功,

關于圖形的說明: 每個框代表一個函式,理論上框的越大表示占用的CPU資源越多, 方框之間的線條代表函式之間的呼叫關系, 線條上的數字表示函式呼叫的次數, 方框中的第一行數字表示當前函式占用CPU的百分比,第二行數字表示當前函式累計占用CPU的百分比,

go-torch和火焰圖

火焰圖(Flame Graph)是 Bredan Gregg 創建的一種性能分析圖表,因為它的樣子近似 ??而得名,上面的 profiling 結果也轉換成火焰圖,如果對火焰圖比較了解可以手動來操作,不過這里我們要介紹一個工具:go-torch,這是 uber 開源的一個工具,可以直接讀取 golang profiling 資料,并生成一個火焰圖的 svg 檔案,

安裝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

安裝 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 == "" {
	return nil, errNoPerlScript
}
if runtime.GOOS == "windows" {
	return runScript("perl", append([]string{flameGraph}, args...), graphInput)
}
  return runScript(flameGraph, args, graphInput)
}

壓測工具wrk

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

使用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就能看到如下火焰圖了,  火焰圖的y軸表示cpu呼叫方法的先后,x軸表示在每個采樣呼叫時間內,方法所占的時間百分比,越寬代表占據cpu時間越多,通過火焰圖我們就可以更清楚的找出耗時長的函式呼叫,然后不斷的修正代碼,重新采樣,不斷優化,

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 才有意義,

練習題

  1. 使用gin框架撰寫一個介面,使用go-wrk進行壓測,使用性能調優工具采集資料繪制出呼叫圖和火焰圖,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/45047.html

標籤:Go

上一篇:輸出控制符的詳解

下一篇:Cookie和Session

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more