Go 程序的性能優化及 pprof 的使用


Go 程序的性能優化及 pprof 的使用

程序的性能優化無非就是對程序占用資源的優化。對於服務器而言,最重要的兩項資源莫過於 CPU 和內存。性能優化,就是在對於不影響程序數據處理能力的情況下,我們通常要求程序的 CPU 的內存占用盡量低。反過來說,也就是當程序 CPU 和內存占用不變的情況下,盡量地提高程序的數據處理能力或者說是吞吐量。

Go 的原生工具鏈中提供了非常多豐富的工具供開發者使用,其中包括 pprof

對於 pprof 的使用要分成下面兩部分來說。

Web 程序使用 pprof

先寫一個簡單的 Web 服務程序。程序在 9876 端口上接收請求。

package main

import (
	"bytes"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"

	_ "net/http/pprof"
)

func main() {
	http.HandleFunc("/test", handler)
	log.Fatal(http.ListenAndServe(":9876", nil))
}

func handler(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if nil != err {
		w.Write([]byte(err.Error()))
		return
	}
	doSomeThingOne(10000)
	buff := genSomeBytes()
	b, err := ioutil.ReadAll(buff)
	if nil != err {
		w.Write([]byte(err.Error()))
		return
	}
	w.Write(b)
}

func doSomeThingOne(times int) {
	for i := 0; i < times; i++ {
		for j := 0; j < times; j++ {

		}
	}
}

func genSomeBytes() *bytes.Buffer {
	var buff bytes.Buffer
	for i := 1; i < 20000; i++ {
		buff.Write([]byte{'0' + byte(rand.Intn(10))})
	}
	return &buff
}

可以看到我們只是簡單地引入了 net/http/pprof ,並未顯示地使用。

啟動程序。

我們用 wrk 來簡單地模擬請求。

wrk -c 400 -t 8 -d 3m http://localhost:9876/test

這時我們打開 http://localhost:9876/debug/pprof,會顯示如下頁面:

用戶可以點擊相應的鏈接瀏覽內容。不過這不是我們重點講述的,而且這些內容看起來並不直觀。

我們打開鏈接 http://localhost:9876/debug/pprof/profile 稍后片刻,可以下載到文件 profile

使用 Go 自帶的 pprof 工具打開。go tool pprof test profile。(proof 后跟的 test 為程序編譯的可執行文件)

輸入 top 命令得到:

可以看到 cpu 占用前 10 的函數,我們可以對此分析進行優化。

只是這樣可能還不是很直觀。

我們輸入命令 web(需要事先安裝 graphviz,macOS 下可以 brew install graphviz),會在瀏覽器中打開界面如下:

可以看到 main.doSomeThingOne 占用了 92.46% 的 CPU 時間,需要對其進行優化。

Web 形式的 CPU 時間圖對於優化已經完全夠用,這邊再介紹一下火焰圖的生成。macOS 推薦使用 go-torch 工具。使用方法和 go tool pprof 相似。

go-torch test profile 會生成 torch.svg 文件。可以用瀏覽器打開,如圖。

剛才只是講了 CPU 的占用分析文件的生成查看,其實內存快照的生成相似。http://localhost:9876/debug/pprof/heap,會下載得到 heap.gz 文件。

我們同樣可以使用 go tool pprof test heap.gz,然后輸入 topweb 命令查看相關內容。

通用程序使用 pprof

我們寫的 Go 程序並非都是 Web 程序,這時候再使用上面的方法就不行了。

我們仍然可以使用 pprof 工具,但引入的位置為 runtime/pprof

這里貼出兩個函數,作為示例:

// 生成 CPU 報告
func cpuProfile() {
	f, err := os.OpenFile("cpu.prof", os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	log.Println("CPU Profile started")
	pprof.StartCPUProfile(f)
	defer pprof.StopCPUProfile()

	time.Sleep(60 * time.Second)
	fmt.Println("CPU Profile stopped")
}

// 生成堆內存報告
func heapProfile() {
	f, err := os.OpenFile("heap.prof", os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	time.Sleep(30 * time.Second)

	pprof.WriteHeapProfile(f)
	fmt.Println("Heap Profile generated")
}

兩個函數分別會生成 cpu.profheap.prof 文件。仍然可以使用 go tool pprof 工具進行分析,在此就不贅述。

Trace 報告

直接貼代碼:

// 生成追蹤報告
func traceProfile() {
	f, err := os.OpenFile("trace.out", os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	log.Println("Trace started")
	trace.Start(f)
	defer trace.Stop()

	time.Sleep(60 * time.Second)
	fmt.Println("Trace stopped")
}

使用工具 go tool trace 進行分析,會得到非常詳細的追蹤報告,供更深入的程序分析優化。由於報告內容比較復雜,且使用方法類似,就不繼續了。讀者可自行嘗試。

貼張網上的圖給大家大概看一下:

參考:https://github.com/caibirdme/hand-to-hand-optimize-go


免責聲明!

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



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