Go 庫(kù)性能分析工具pprof
場(chǎng)景
我們一般沒(méi)必要過(guò)度優(yōu)化 Go 程序性能。但是真正需要時(shí),Go 提供的 pprof 工具能幫我們快速定位到問(wèn)題。比如,我們團(tuán)隊(duì)之前有一個(gè)服務(wù),在本地和測(cè)試環(huán)境沒(méi)問(wèn)題,一到灰度環(huán)境,就報(bào) cpu 負(fù)載過(guò)高,后經(jīng)排查,發(fā)現(xiàn)某處代碼死循環(huán)了。我把代碼簡(jiǎn)化成如下:
// 處理某些業(yè)務(wù),真實(shí)的代碼中這個(gè)死循環(huán)很隱蔽
func retrieveSomeThing() {
for {}
}
// 處理其他的一些業(yè)務(wù),無(wú)意義,用于后續(xù)做例子
func doSomeThing() {
do1()
for i := 0; i < 200000000; i++ {}
do2()
}
// 無(wú)意義
func do1() {
for i := 0; i < 200000000; i++ {}
}
// 無(wú)意義
func do2() {
for i := 0; i < 200000000; i++ {}
}
func main() {
go retrieveSomeThing()
go doSomeThing()
// 阻塞一下
time.Sleep(3 * time.Second)
}
解決問(wèn)題前,先介紹下 pprof。
pprof
pprof 包會(huì)輸出運(yùn)行時(shí)的分析數(shù)據(jù)(profiling data),這些數(shù)據(jù)可以被 pprof 的可視化工具解析。Go 標(biāo)準(zhǔn)庫(kù)主要提供了兩個(gè)包:
runtime/pprof通過(guò)寫(xiě)入到文件的方式暴露 profile 數(shù)據(jù);net/http/pprof通過(guò) http 服務(wù)暴露 profile 數(shù)據(jù),適用于守護(hù)進(jìn)程。
生成 profile 文件
CPU 性能分析
在 runtime/pprof 中,使用StartCPUProfile開(kāi)啟 CPU 性能分析。退出程序前,需要調(diào)用StopCPUProfile把采樣數(shù)據(jù) flush 到輸出文件。
采樣的頻率默認(rèn)是 100 Hz(每秒 100 次)。
// 輸出到標(biāo)準(zhǔn)輸出,一般是指定文件
if err := pprof.StartCPUProfile(os.Stdout); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
內(nèi)存性能分析
調(diào)用 WriteHeapProfile 開(kāi)啟內(nèi)存性能分析:
// 輸出到標(biāo)準(zhǔn)輸出,一般是指定文件
if err := pprof.WriteHeapProfile(os.Stdout); err != nil {
log.Fatal("could not write memory profile: ", err)
}
}
分析 profile 文件 && 優(yōu)化代碼
以開(kāi)篇的代碼為例,由于是 CPU 過(guò)載,我們可以在 main 函數(shù)開(kāi)啟 CPU Profile:
// 通過(guò)參數(shù)指定 cpu profile 輸出的文件
var cpuprofile = flag.String("cpuprofile", "", "write cpu 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)
}
// 開(kāi)啟 CPU 分析
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
}
// 業(yè)務(wù)代碼
go retrieveSomeThing()
go doSomeThing()
// 模擬阻塞
time.Sleep(5 * time.Second)
}
我們執(zhí)行命令,輸出 profile 文件到 cpu.prof。
go run main.go -cpuprofile cpu.prof
go tool pprof
Go 提供性能解析工具:go tool pprof。我們使用 go tool 打開(kāi) profile 文件。
> go tool pprof cpu.prof Type: cpu Time: Nov 16, 2022 at 1:40pm (CST) Duration: 5.17s, Total samples = 4.54s (87.75%) Entering interactive mode (type "help" for commands, "o" for options) (pprof)
這是個(gè)交互式的界面,輸入help可以查看所有命令。
top 命令
我們使用 topN 命令,查看根據(jù) flat 從大到小排序的前 N 條數(shù)據(jù)。
(pprof) top10
Showing nodes accounting for 4650ms, 100% of 4650ms total
flat flat% sum% cum cum%
4220ms 90.75% 90.75% 4450ms 95.70% main.retrieveSomeThing
230ms 4.95% 95.70% 230ms 4.95% runtime.asyncPreempt
80ms 1.72% 97.42% 200ms 4.30% main.doSomeThing
70ms 1.51% 98.92% 70ms 1.51% main.do2 (inline)
50ms 1.08% 100% 50ms 1.08% main.do1 (inline)
top 命令返回?cái)?shù)據(jù)有5個(gè)指標(biāo):
- flat : 本函數(shù)占用的 CPU 時(shí)間,不包括調(diào)用函數(shù)的時(shí)間;
- flat% : flat 占的百分比;
- sum% : 前面 flat% 的總和;
- cum : 累計(jì)時(shí)間,包括調(diào)用的函數(shù)的時(shí)間;
- cum% : cum 的百分比。
以main.doSomeThing(排第三的函數(shù))為例子,耗時(shí)為:
func doSomeThing() { // flat: 80ms cum: 200ms
do1() // 執(zhí)行時(shí)間 50ms
for i := 0; i < 200000000; i++ {} // 執(zhí)行時(shí)間 80ms
do2() // 執(zhí)行時(shí)間 70ms
}
doSomeThing 的 flat 的值為:
for i := 0; i < 200000000; i++ {}的執(zhí)行時(shí)間(80ms),不包括do1和do2的時(shí)間。
doSomeThing 的 cum 的值為:
cum(200ms) = doSomething的flat(80ms) + do1的flat(50ms) + do2的flat(70ms)
ps: top 可以使用 -cum 參數(shù)來(lái)指定,根據(jù) cum 排序。
list 命令
明白了 top 的指標(biāo)的意思,我們關(guān)注到,排在 top1 的函數(shù)是 retrieveSomeThing??梢允褂?list 命令,查看 retrieveSomeThing 耗時(shí):
(pprof) list retrieveSomeThing
Total: 4.65s
ROUTINE ======================== main.retrieveSomeThing in /xxxx/pprof_note/pprof/main.go
4.22s 4.45s (flat, cum) 95.70% of Total
10ms 10ms 1:package main
. . 2:
. . 3:import (
. . 4: "flag"
. . 5: "log"
. . 6: "os"
. . 7: "runtime/pprof"
. . 8: "time"
. . 9:)
. . 10:
. . 11:// 處理某些業(yè)務(wù),真實(shí)的代碼中這個(gè)死循環(huán)很隱蔽
. . 12:func retrieveSomeThing() {
4.21s 4.44s 13: for {
. . 14: }
. . 15:}
. . 16:
. . 17:// 處理其他的一些業(yè)務(wù),無(wú)意義,用于后續(xù)做例子
. . 18:func doSomeThing() {
我們定位到13行需要優(yōu)化。
總結(jié)
pprof 還有很多玩法,包括其他的性能指標(biāo),go tool 的其他命令,profile 文件的可視化等。這個(gè)留給讀者自行擴(kuò)展閱讀。
本文主要參考了 Russ Cox 大神的文章:《Profiling Go Programs》 (go.dev/blog/pprof)… 文章為反駁 "Go性能不如其他語(yǔ)言"的觀點(diǎn),借助 pprof 大幅度優(yōu)化了程序的運(yùn)行時(shí)間和內(nèi)存。
以上就是Go 庫(kù)性能分析工具pprof的詳細(xì)內(nèi)容,更多關(guān)于Go pprof性能分析的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于升級(jí)go1.18的goland問(wèn)題詳解
作為一個(gè)go語(yǔ)言程序員,覺(jué)得自己有義務(wù)為go新手開(kāi)一條更簡(jiǎn)單便捷的上手之路,下面這篇文章主要給大家介紹了關(guān)于升級(jí)go1.18的goland問(wèn)題的相關(guān)資料,需要的朋友可以參考下2022-11-11
golang jsoniter extension 處理動(dòng)態(tài)字段的實(shí)現(xiàn)方法
這篇文章主要介紹了golang jsoniter extension 處理動(dòng)態(tài)字段的實(shí)現(xiàn)方法,我們使用實(shí)例級(jí)別的 extension, 而非全局,可以針對(duì)不同業(yè)務(wù)邏輯有所區(qū)分,jsoniter 包提供了比較完善的定制能力,通過(guò)例子可以感受一下擴(kuò)展性,需要的朋友可以參考下2023-04-04
Go語(yǔ)言學(xué)習(xí)之將mp4通過(guò)rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法
對(duì)音視頻一直是小白,決定沉下心來(lái),好好研究一下音視頻知識(shí),下面這篇文章主要給大家介紹了關(guān)于Go語(yǔ)言學(xué)習(xí)之將mp4通過(guò)rtmp推送流媒體服務(wù)的實(shí)現(xiàn)方法,需要的朋友可以參考下2022-12-12
go mod 安裝依賴(lài) unkown revision問(wèn)題的解決方案
這篇文章主要介紹了go mod 安裝依賴(lài) unkown revision問(wèn)題的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05

