Go 效能工具小抄

Cluas發表於2021-06-16

Go 有很多工具可以讓你瞭解你的應用程式可能在哪裡花費 CPU 時間或分配記憶體。我不是每天都使用這些工具,所以我每次都是在尋找同樣的東西。這篇文章的目的是為這些 Go 所提供的工具提供一個參考文獻。

我們將使用 https://gitlab.com/steveazz-blog/go-performance-tools-cheat-sheet 作為一個演示專案,同樣的事情有 3 種實現方式,每一種都比前一種效能更好。

基準測試 (Benchmarks)

最流行的方法之一是使用 Go 中內建的 基準測試 來看看你是否改進了什麼。

在我們的演示專案中,已經有了 可用的基準測試 我們可以用一個命令執行它們。

go test -bench=. -test.benchmem  ./rand/

goos: darwin
goarch: amd64
pkg: gitlab.com/steveazz/blog/go-performance-tools-cheat-sheet/rand
cpu: Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
BenchmarkHitCount100-8              3020            367016 ns/op          269861 B/op       3600 allocs/op
BenchmarkHitCount1000-8              326           3737517 ns/op         2696308 B/op      36005 allocs/op
BenchmarkHitCount100000-8              3         370797178 ns/op        269406189 B/op   3600563 allocs/op
BenchmarkHitCount1000000-8             1        3857843580 ns/op        2697160640 B/op 36006111 allocs/op
PASS
ok      gitlab.com/steveazz/blog/go-performance-tools-cheat-sheet/rand 8.828s

注意: -test.benchmem 是一個可選的標誌,用於顯示記憶體分配。

仔細看一下每一欄的含義:

BenchmarkHitCount100-8              3020            367016 ns/op             269861 B/op               3600 allocs/op
^------------------^ ^                ^                   ^                      ^                          ^
         |           |                |                   |                      |                          |
        名稱       CPU數量          總執行次數          每次操作的納秒數          每次操作的位元組數             每次操作的記憶體分配數

比較基準測試 (Benchmarks)

Go 建立了 perf 它提供了 benchstat 這樣你就可以一起比較 benchmark 輸出,它將給你展示出它們之間的差異。

例如,讓我們比較一下 mainbest 分支。

# 執行 `main` 分支的基準測試
git checkout main
go test -bench=. -test.benchmem -count=5 ./rand/ > old.txt

# 執行 `best` 分支的基準測試
git checkout best
go test -bench=. -test.benchmem -count=5 ./rand/ > new.txt

# 比較兩個基準測試結果
benchstat old.txt new.txt
name               old time/op    new time/op    delta
HitCount100-8         366µs ± 0%     103µs ± 0%  -71.89%  (p=0.008 n=5+5)
HitCount1000-8       3.66ms ± 0%    1.06ms ± 5%  -71.13%  (p=0.008 n=5+5)
HitCount100000-8      367ms ± 0%     104ms ± 1%  -71.70%  (p=0.008 n=5+5)
HitCount1000000-8     3.66s ± 0%     1.03s ± 1%  -71.84%  (p=0.016 n=4+5)

name               old alloc/op   new alloc/op   delta
HitCount100-8         270kB ± 0%      53kB ± 0%  -80.36%  (p=0.008 n=5+5)
HitCount1000-8       2.70MB ± 0%    0.53MB ± 0%  -80.39%  (p=0.008 n=5+5)
HitCount100000-8      270MB ± 0%      53MB ± 0%  -80.38%  (p=0.008 n=5+5)
HitCount1000000-8    2.70GB ± 0%    0.53GB ± 0%  -80.39%  (p=0.016 n=4+5)

name               old allocs/op  new allocs/op  delta
HitCount100-8         3.60k ± 0%     1.50k ± 0%  -58.33%  (p=0.008 n=5+5)
HitCount1000-8        36.0k ± 0%     15.0k ± 0%  -58.34%  (p=0.008 n=5+5)
HitCount100000-8      3.60M ± 0%     1.50M ± 0%  -58.34%  (p=0.008 n=5+5)
HitCount1000000-8     36.0M ± 0%     15.0M ± 0%  -58.34%  (p=0.008 n=5+5)

注意,我們傳遞了 -count 標誌來多次執行基準測試,這樣它就可以得到執行的平均值。

pprof

Go 有自己的剖析器,它可以讓你更好地瞭解 CPU 時間花在哪裡,或者應用程式在哪裡分配記憶體。Go 在一段時間內對這些進行取樣,例如,它將在 X 秒內每隔 X 納秒檢視 CPU/記憶體的使用情況。

生成剖析檔案 (Profiles)

Benchmarks

你可以使用我們在演示專案中的基準來生成剖析檔案。

CPU:

go test -bench=. -cpuprofile cpu.prof ./rand/

Memory:

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

net/http/pprof package

如果你正在編寫一個 webserver,你可以匯入 net/http/pprof 它將在 DefaultServeMux 上暴露/debug/pprof HTTP 端點,就像我們在 示例應用 中做的那樣。

確保你的應用程式不是空閒著它需要正在執行或接收請求,以便剖析器能夠對呼叫進行取樣,否則你可能會因為應用程式是空閒的而最終得到一個空的剖析檔案。

# CPU剖析檔案
curl http://127.0.0.1:8080/debug/pprof/profile > /tmp/cpu.prof
# 堆剖析檔案
curl http://127.0.0.1:8080/debug/pprof/heap > /tmp/heap.prof
# 記憶體分配剖析檔案
curl http://127.0.0.1:8080/debug/pprof/allocs > /tmp/allocs.prof

如果你訪問/debug/pprof,它將給出所有可用的端點的列表和它們的含義。

runtime/pprof 包

這與 net/http/pprof 類似,將它新增到你的應用程式中,但不是為所有的專案新增,你可以指定一個特定的程式碼路徑,在那個路徑生成剖析檔案。當你只對你的應用程式的某一部分感興趣,並且你只想對應用程式的那一部分進行取樣時,這就很有用。要閱讀如何使用它,請檢視 go 的參考文件

你也可以用這個來 給應用加上標籤 這可以幫助你更好地瞭解概況。

閱讀剖析檔案 (Profiles)

現在我們知道了如何生成剖析檔案,讓我們看看如何讀取它們以瞭解我們的應用程式正在做什麼。

我們將使用的命令是 go tool pprof

呼叫圖

例如,為了使用我們在演示程式中 registered/debug/pprof端點,我們可以直接傳入 HTTP 端點。

# 開啟新的瀏覽器視窗,檢視30秒後的呼叫圖。
go tool pprof -http :9402 http://127.0.0.1:8080/debug/pprof/profile

另一個選擇是使用 curl 下載剖析檔案,然後使用 go tool命令,這對於從沒有暴露在公共網際網路上的生產端點獲取剖析檔案可能很有用。

# 在伺服器上檢視。
curl http://127.0.0.1:8080/debug/pprof/profile > /tmp/cpu.prof
# 從伺服器上下載後,在本地檢視。
go tool pprof -http :9402 /tmp/cpu.prof

注意,在所有的命令中,我們都傳遞了-http標誌,這是可選的,因為在預設情況下,這將開啟 CLI 介面。

下面你可以看到我們的演示應用程式呼叫圖。為了更好地理解它的含義,你應該閱讀 解釋呼叫圖

我們的演示應用程式的呼叫圖的示例

火焰圖

呼叫圖對於檢視程式正在呼叫的內容很有用,也可以幫助你瞭解應用程式正在花費的時間。瞭解 CPU 時間或記憶體分配去向的另一種方法是使用火焰圖。

使用同一個演示程式,讓我們再次執行 go tool pprof:

# 來自HTTP端點
go tool pprof -http :9402 http://127.0.0.1:8080/debug/pprof/profile
# 來自本地的剖析檔案
go tool pprof -http :9402 /tmp/cpu.prof

然而,這一次我們將使用頂部導航欄,前往 View > Flame Graph

導航到火焰圖

然後你應該看到類似下面的東西:

火焰圖示例

為了讓你更好地瞭解如何閱讀火焰圖,你可以檢視一下 什麼是火焰圖,怎麼去讀火焰圖 (RubyConf2017 上的議題)

你也可以使用 speedscope 這是一個與語言無關的應用程式,可以從剖析檔案中生成火焰圖,它比 Go 提供的互動性更強一些。

speedscope示例

比較剖析檔案 (Profiles)

go tool pprof 也允許你使用 比較剖析檔案 來顯示 1 個剖析檔案和另一個剖析檔案之間的差異。

再次使用我們的演示專案,我們可以為 "main" 和 "best" 分支生成剖析檔案。

# 執行 `main` 分支,生成剖析檔案。
curl http://127.0.0.1:8080/debug/pprof/profile > /tmp/main.prof

# 執行 `best` 分支,生成剖析檔案。
curl http://127.0.0.1:8080/debug/pprof/profile > /tmp/best.prof

# 比較剖析檔案。
go tool pprof -http :9402 --diff_base=/tmp/main.prof /tmp/best.prof

示例剖析檔案對比

追蹤 (Traces)

我們需要經歷的最後一個工具是 CPU 追蹤器。這可以讓你準確地瞭解在程式執行過程中發生了什麼。它可以告訴你哪些核心是閒置的,哪些核心是忙碌的。如果你正在除錯一些沒有達到預期效能的併發程式碼,這就非常好用。

再次使用我們的演示程式,讓我們使用net/http/pprof新增的debug/pprof/trace端點來獲得 CPU 跟蹤。

curl http://127.0.0.1:8080/debug/pprof/trace > /tmp/best.trace

go tool trace /tmp/best.trace

關於追蹤器的更詳細的解釋可以在 go 程式設計師學院部落格 上找到。

追蹤(trace)示例

資源

文中提到的連結

更多原創文章乾貨分享,請關注公眾號
  • Go 效能工具小抄
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章