Golang使用pprof和trace進(jìn)行診斷和修復(fù)性能問題
軟件開發(fā)嚴(yán)重依賴調(diào)試技術(shù),這對(duì)于有效處理性能問題至關(guān)重要。用戶在遇到程序執(zhí)行緩慢時(shí)會(huì)感到沮喪,這凸顯了通過調(diào)試工具有效識(shí)別和解決潛在問題的重要性。
但是,由于軟件的創(chuàng)建和實(shí)現(xiàn)過程中涉及龐大的代碼庫(kù)或復(fù)雜的系統(tǒng),因此調(diào)試軟件中的性能問題可能很困難。
在 Go 中,開發(fā)人員可以使用強(qiáng)大的內(nèi)置工具來幫助診斷和修復(fù)性能問題。其中兩個(gè)工具是 pprof
和 trace
包。
pprof
包允許您分析和分析 Go 程序的執(zhí)行,而該 trace
包允許您跟蹤和可視化事件和 goroutine
活動(dòng)。當(dāng)這些工具一起使用時(shí),可以幫我們快速定位 Go 程序中導(dǎo)致性能低下的代碼。
了解性能問題
Go 程序或任何軟件應(yīng)用程序中的性能問題都會(huì)對(duì)用戶體驗(yàn)產(chǎn)生重大影響。Go 程序中的性能問題可能由于多種原因而發(fā)生。在本節(jié)中,我們將介紹性能問題的一些最常見原因以及它們?nèi)绾斡绊懴到y(tǒng)。
- 低效算法:低效算法會(huì)對(duì)性能產(chǎn)生重大影響,尤其是在處理大型數(shù)據(jù)集時(shí)。這些算法會(huì)占用額外的 CPU 周期和內(nèi)存資源,這可能會(huì)降低整個(gè)應(yīng)用程序的速度。比如暴力搜索方法和無(wú)效的排序算法。
- 阻塞操作:應(yīng)用程序可能偶爾會(huì)等待 I/O 活動(dòng)完成,例如在磁盤上讀取或?qū)懭霐?shù)據(jù)或連接到網(wǎng)絡(luò)。在此過程中,可能會(huì)發(fā)生阻塞操作并導(dǎo)致執(zhí)行延遲,從而導(dǎo)致性能下降。當(dāng)應(yīng)用程序被阻塞時(shí),它無(wú)法執(zhí)行其他有用的任務(wù),從而導(dǎo)致整體性能下降。
- 內(nèi)存使用率過高:使用大量?jī)?nèi)存的 Go 應(yīng)用可能會(huì)導(dǎo)致性能問題,尤其是在資源不足的系統(tǒng)上。如果應(yīng)用程序消耗的內(nèi)存多于系統(tǒng)的可用內(nèi)存,則系統(tǒng)可能會(huì)開始交換到磁盤,從而大大降低應(yīng)用程序的性能。如果應(yīng)用程序不能有效地管理內(nèi)存,從而導(dǎo)致內(nèi)存泄漏和其他問題,也會(huì)發(fā)生這種情況。
goroutine 泄漏也會(huì)導(dǎo)致內(nèi)存使用率過高。另外,一些中間價(jià)比如 Elasticsearch 等,則建議直接禁用 swap,因?yàn)?swap 會(huì)導(dǎo)致性能下降,取而代之的是給它足夠大的內(nèi)容。
性能低下的應(yīng)用程序會(huì)導(dǎo)致用戶體驗(yàn)差,從而導(dǎo)致用戶流失。為了獲得最佳體驗(yàn),優(yōu)化 Go 應(yīng)用程序至關(guān)重要。
使用 pprof 診斷性能問題
pprof
是 Go 中的一個(gè)內(nèi)置包,它為開發(fā)人員提供了一個(gè)分析工具,用于觀測(cè)他們的 Go 程序如何使用 CPU 和內(nèi)存。然后收集和分析來自此測(cè)量的數(shù)據(jù)。借助 pprof
軟件包,開發(fā)人員可以輕松測(cè)量和識(shí)別消耗比正常情況更多的 CPU 內(nèi)存的函數(shù),以及分配最多內(nèi)存的程序部分。
讓我們假設(shè)一個(gè)轉(zhuǎn)賬 App 使用 Go,并且它具有允許用戶使用二維碼向朋友匯款的功能。更新該功能后,其開發(fā)人員注意到該應(yīng)用程序的運(yùn)行速度比平時(shí)慢得多,40% 的用戶抱怨掃描二維碼時(shí)延遲長(zhǎng)達(dá) 15 秒,有時(shí)付款失敗。為了正確分析問題,開發(fā)團(tuán)隊(duì)可以在用戶掃描二維碼時(shí)使用 pprof
生成 CPU 分析文件。通過分析文件,他們可能會(huì)發(fā)現(xiàn)哪些函數(shù)占用了過多的 CPU 內(nèi)存或哪些算法效率低下。在發(fā)現(xiàn)問題并修復(fù)問題后,他們可以再次測(cè)試和使用 pprof
,以確保性能得到提高,體驗(yàn)更快、更無(wú)縫。
pprof 的 profile 類型
- CPU:用于分析程序的 CPU 使用情況。衡量函數(shù)如何消耗不同的 CPU 時(shí)間,從而更容易識(shí)別哪些函數(shù)消耗更多時(shí)間,這些就可能是潛在的瓶頸。
- Memory:用于分析程序的內(nèi)存使用情況。衡量應(yīng)用程序如何使用內(nèi)存以及應(yīng)用程序的哪些部分分配更多內(nèi)存。
- Block(阻塞):顯示程序阻塞的位置(例如
I/O
或同步原語(yǔ)),從而更容易識(shí)別并發(fā)低下的區(qū)域。 - Goroutine(協(xié)程):通過返回正在運(yùn)行、阻塞和等待的狀態(tài)的 Goroutine,可以輕松檢測(cè)到并發(fā)低下的區(qū)域。
- Trace:捕獲程序執(zhí)行期間發(fā)生的事件的詳細(xì)日志,例如 goroutine 創(chuàng)建和銷毀、調(diào)度、網(wǎng)絡(luò)活動(dòng)和阻塞操作。它在詳細(xì)分析應(yīng)用程序的性能時(shí)非常有用。
分析 profile
我們下面以一個(gè)例子來講解一下:
package main import ( "fmt" "math/rand" "os" "runtime/pprof" ) func main() { // 創(chuàng)建一個(gè)保存 CPU 分析結(jié)果的文件 f, err := os.Create("profile.prof") if err != nil { panic(err) } defer f.Close() // 開始采集 CPU 性能指標(biāo) if err := pprof.StartCPUProfile(f); err != nil { panic(err) } defer pprof.StopCPUProfile() // 模擬耗 CPU 的操作 for i := 0; i < 1000000; i++ { n := rand.Intn(100) _ = square(n) } } func square(n int) int { return n * n }
在上面的代碼中:
main
函數(shù)生成一個(gè)介于 1 和 1000 之間的隨機(jī)數(shù),然后計(jì)算其平方根。pprof.StartCPUProfile(f)
函數(shù)啟動(dòng) CPU 分析,從而創(chuàng)建可以在以后分析的 profile 文件。defer pprof.StopCPUProfile()
語(yǔ)句確保在程序結(jié)束時(shí)停止 CPU 分析,無(wú)論程序是正常終止還是由于錯(cuò)誤。- 我們調(diào)用
rand.Intn(100)
1000000 次來模擬 CPU 密集型任務(wù)。
接下來,我們執(zhí)行這個(gè)程序:
go run main.go
程序運(yùn)行結(jié)束后,會(huì)生成一個(gè)名為 profile.pprof
的文件,這個(gè)文件包含了 CPU 分析的數(shù)據(jù)。我們可以使用 go tool pprof
命令來分析這個(gè)文件:
go tool pprof profile.prof
接下來,會(huì)輸出如下內(nèi)容,并進(jìn)入了一個(gè)交互式的命令行:
Type: cpu
Time: Jan 15, 2024 at 5:17pm (CST)
Duration: 205.21ms, Total samples = 10ms ( 4.87%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
我們可以接著輸入一些命令,來查看 profile 的數(shù)據(jù):
比如,我們可以輸入 top
來查看最耗 CPU 的函數(shù):
(pprof) top
Showing nodes accounting for 10ms, 100% of 10ms total
flat flat% sum% cum cum%
10ms 100% 100% 10ms 100% math/rand.(*Rand).Intn
0 0% 100% 10ms 100% main.main
0 0% 100% 10ms 100% math/rand.Intn (inline)
0 0% 100% 10ms 100% runtime.main
(pprof)
分析內(nèi)存
若要獲取內(nèi)存配置文件,請(qǐng)修改代碼以使用函數(shù) pprof.WriteHeapProfile()
將堆配置文件寫入文件。在生成隨機(jī)數(shù)并計(jì)算其平方后,您需要添加代碼以將內(nèi)存配置文件寫入文件(mem.prof
)。您還將添加一個(gè) time.Sleep(5 * time.Second)
調(diào)用,以便有時(shí)間將內(nèi)存配置文件寫入文件。在下面找到代碼的更新版本:
package main import ( "fmt" "math/rand" "os" "runtime/pprof" "time" ) func main() { // 創(chuàng)建一個(gè)保存 CPU 分析結(jié)果的文件 cpuProfileFile, err := os.Create("cpu.prof") if err != nil { panic(err) } defer cpuProfileFile.Close() // 開始采集 CPU 性能指標(biāo) if err := pprof.StartCPUProfile(cpuProfileFile); err != nil { panic(err) } defer pprof.StopCPUProfile() // 模擬耗 CPU 的操作 for i := 0; i < 10; i++ { n := rand.Intn(100) s := square(n) fmt.Printf("%d^2 = %d\n", n, s) } // 創(chuàng)建一個(gè)保存內(nèi)存分析結(jié)果的文件 memProfileFile, err := os.Create("mem.prof") if err != nil { panic(err) } defer memProfileFile.Close() // 將內(nèi)存分析結(jié)果寫入文件 if err := pprof.WriteHeapProfile(memProfileFile); err != nil { panic(err) } fmt.Println("Memory profile written to mem.prof") time.Sleep(5 * time.Second) } func square(n int) int { return n * n }
輸出:
31^2 = 961
83^2 = 6889
88^2 = 7744
86^2 = 7396
14^2 = 196
99^2 = 9801
42^2 = 1764
29^2 = 841
86^2 = 7396
86^2 = 7396
Memory profile written to mem.prof
運(yùn)行 go run main.go
后,將生成一個(gè) mem.prof
文件。在交互式 shell
中,鍵入 top
以分析程序的內(nèi)存使用情況。若要顯示此交互式 shell
,請(qǐng)運(yùn)行以下命令:
go tool pprof mem.prof
要按 CPU 使用率顯示排名靠前的函數(shù),請(qǐng)鍵入 top
命令:
? go tool pprof mem.prof
Type: inuse_space
Time: Jan 15, 2024 at 5:22pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.72MB, 100% of 1.72MB total
flat flat% sum% cum cum%
1.72MB 100% 100% 1.72MB 100% runtime/pprof.StartCPUProfile
0 0% 100% 1.72MB 100% main.main
0 0% 100% 1.72MB 100% runtime.main
(pprof)
從上面的示例中可以看出,pprof
可以讓我們很清楚地知道哪些函數(shù)占用了大量的內(nèi)存或者 CPU。因此,通過將 profile
納入開發(fā)過程,可以很容易地主動(dòng)識(shí)別和解決性能問題,從而實(shí)現(xiàn)更快、更高效的應(yīng)用程序。
在第一個(gè)示例中,我們了解了如何使用 pprof
工具創(chuàng)建 CPU 分析文件并對(duì)其進(jìn)行分析。輸出顯示每個(gè)函數(shù)的調(diào)用次數(shù),以及執(zhí)行每個(gè)函數(shù)所花費(fèi)的總時(shí)間。這使我們能夠識(shí)別消耗最多 CPU 時(shí)間的函數(shù),并可能對(duì)其進(jìn)行優(yōu)化。在第二個(gè)示例中,輸出顯示了每個(gè)函數(shù)的內(nèi)存使用情況,包括分配的數(shù)量和分配的字節(jié)數(shù)。這使我們能夠識(shí)別使用過多內(nèi)存的函數(shù),并可能對(duì)其進(jìn)行優(yōu)化以減少內(nèi)存使用。
使用 trace 追蹤
有時(shí),我們需要有關(guān)程序如何運(yùn)行的更多詳細(xì)信息。在這種情況下,trace
包是一個(gè)非常強(qiáng)大和有用的工具。在本節(jié)中,我們將對(duì)其進(jìn)行介紹。
trace
是一種工具,可讓您收集有關(guān)程序運(yùn)行方式的詳細(xì)信息。它對(duì)于理解 goroutine
是如何創(chuàng)建和調(diào)度的、通道的使用方式以及網(wǎng)絡(luò)請(qǐng)求的處理方式等內(nèi)容非常有用。它提供了程序執(zhí)行的時(shí)間線視圖,可用于識(shí)別一段時(shí)間內(nèi)的性能問題和其他類型的錯(cuò)誤。
trace
可以收集有關(guān)程序運(yùn)行時(shí)發(fā)生的各種事件的數(shù)據(jù)。這些事件包括:Goroutine 創(chuàng)建、銷毀、阻塞、取消阻塞、網(wǎng)絡(luò)活動(dòng)和垃圾回收。每個(gè) trace 事件都分配了一個(gè)時(shí)間戳和一個(gè) goroutine ID,允許您查看事件的順序以及它們之間的關(guān)系。
分析 trace 追蹤數(shù)據(jù)
首先,我們將創(chuàng)建一個(gè)新的 go 文件,將其命名為 trace.go
。若要生成跟蹤數(shù)據(jù),請(qǐng)導(dǎo)入 runtime/trace
包并在程序開始時(shí)調(diào)用 trace.Start
。若要停止跟蹤收集,請(qǐng)?jiān)诔绦蚪Y(jié)束時(shí)調(diào)用 trace.Stop
。下面是它的樣子:
package main import ( "fmt" "math/rand" "os" "runtime/pprof" "runtime/trace" ) func main() { // 創(chuàng)建一個(gè)保存 CPU 分析結(jié)果的文件 f, err := os.Create("profile.prof") if err != nil { panic(err) } defer f.Close() // 開始采集 CPU 性能指標(biāo) if err := pprof.StartCPUProfile(f); err != nil { panic(err) } defer pprof.StopCPUProfile() // 創(chuàng)建一個(gè)保存 trace 追蹤結(jié)果的文件 traceFile, err := os.Create("trace.out") if err != nil { panic(err) } defer traceFile.Close() if err := trace.Start(traceFile); err != nil { panic(err) } defer trace.Stop() // 模擬耗 CPU 的操作 for i := 0; i < 10; i++ { n := rand.Intn(100) _ = square(n) } } func square(n int) int { return n * n }
運(yùn)行以下命令以啟動(dòng)程序:
go run main.go
要分析跟蹤數(shù)據(jù),可以使用 go tool trace
命令,后跟跟蹤文件的名稱:
go tool trace trace.out
這將啟動(dòng)基于 Web 的跟蹤數(shù)據(jù)可視化,您可以使用它來了解程序的運(yùn)行方式并識(shí)別性能問題。
您還可以查看有關(guān)各種 goroutine 以及各種進(jìn)程如何運(yùn)行的詳細(xì)信息!Trace 是了解各種流事件、goroutine 分析等等的絕佳工具!
分析和修復(fù)性能問題
使用 pprof
和 trace
收集性能數(shù)據(jù)后,下一步是分析數(shù)據(jù)并確定可能的性能問題。
要解釋 pprof
的輸出,首先需要了解可用的各種類型的分析數(shù)據(jù)。最常見的配置文件類型是 CPU 和內(nèi)存配置文件,就像前面引用的示例一樣。通過分析這些配置文件,可以識(shí)別消耗大量資源并可能成為潛在瓶頸的功能。 pprof
還可以生成其他類型的配置文件,例如互斥鎖爭(zhēng)用和阻塞配置文件,這有助于確定同步和阻塞問題。例如,較高的互斥鎖爭(zhēng)用率可能表明多個(gè) goroutine
正在爭(zhēng)用同一個(gè)鎖,這可能導(dǎo)致阻塞和性能不佳。
如前所述,跟蹤數(shù)據(jù)包含有關(guān)應(yīng)用程序行為的更全面的數(shù)據(jù),例如 goroutines、阻塞操作和網(wǎng)絡(luò)流量。跟蹤數(shù)據(jù)分析可用于檢測(cè)延遲源和其他性能問題,例如網(wǎng)絡(luò)延遲過長(zhǎng)或選擇了效率低下的算法。
一旦確定了性能問題,有幾種方法可以優(yōu)化性能。一種常見的策略是通過重用對(duì)象來減少內(nèi)存分配,同時(shí)減少大型數(shù)據(jù)結(jié)構(gòu)的使用。通過減少可分配的內(nèi)存量和垃圾回收量,可以降低 CPU 使用率并提高整體程序性能。
另一種方法是使用異步 I/O
或非阻塞操作來減少阻塞操作,例如文件 I/O
或網(wǎng)絡(luò)通信。這有助于減少程序等待 I/O
操作完成所花費(fèi)的時(shí)間,并提高整體程序吞吐量。
此外,優(yōu)化算法和數(shù)據(jù)結(jié)構(gòu)可以顯著提高性能。通過選擇更有效的算法和數(shù)據(jù)結(jié)構(gòu),可以減少完成操作所需的 CPU 時(shí)間,并提高整體程序性能。
總結(jié)
優(yōu)化 Go 應(yīng)用程序中的性能以確保它們高效且有效地運(yùn)行非常重要。通過這樣做,我們可以改善用戶體驗(yàn),降低運(yùn)行應(yīng)用程序的成本,并提高代碼的整體質(zhì)量。我們可以使用 pprof
和 trace
工具來分析 CPU 和內(nèi)存使用情況,并識(shí)別 Go 應(yīng)用程序中的瓶頸和其他問題。然后,我們可以根據(jù)這些工具的輸出對(duì)代碼進(jìn)行有針對(duì)性的改進(jìn),例如減少內(nèi)存分配、最小化阻塞操作和優(yōu)化算法。分析工具(如 pprof
和 trace
)對(duì)于識(shí)別和解決性能問題至關(guān)重要。
以上就是Golang使用pprof和trace進(jìn)行診斷和修復(fù)性能問題的詳細(xì)內(nèi)容,更多關(guān)于Go pprof trace的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go?sync包中的互斥鎖Mutex和等待組WaitGroup使用詳解
這篇文章主要為大家介紹了go?sync包中的互斥鎖Mutex和等待組WaitGroup使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08golang使用tail實(shí)現(xiàn)追蹤文件變更
這篇文章主要為大家介紹了golang如何借助 github.com/hpcloud/tail ,實(shí)現(xiàn)實(shí)時(shí)追蹤文件變更,達(dá)到類似shell命令tail -f的效果,感興趣的小伙伴可以了解一下2023-08-08Go語(yǔ)言實(shí)現(xiàn)lru淘汰策略和超時(shí)過期
緩存的大小是有限的,當(dāng)添加數(shù)據(jù)發(fā)現(xiàn)剩余緩存不夠時(shí),需要淘汰緩存中的部分?jǐn)?shù)據(jù),本文主要介紹了Go語(yǔ)言實(shí)現(xiàn)lru淘汰策略和超時(shí)過期,感興趣的可以了解一下2024-02-02go語(yǔ)言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù)
這篇文章主要為大家介紹了go語(yǔ)言搬磚之go jmespath實(shí)現(xiàn)查詢json數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06