網站首頁 編程語言 正文
場景
我們一般沒必要過度優化 Go 程序性能。但是真正需要時,Go 提供的 pprof 工具能幫我們快速定位到問題。比如,我們團隊之前有一個服務,在本地和測試環境沒問題,一到灰度環境,就報 cpu 負載過高,后經排查,發現某處代碼死循環了。我把代碼簡化成如下:
// 處理某些業務,真實的代碼中這個死循環很隱蔽 func retrieveSomeThing() { for {} } // 處理其他的一些業務,無意義,用于后續做例子 func doSomeThing() { do1() for i := 0; i < 200000000; i++ {} do2() } // 無意義 func do1() { for i := 0; i < 200000000; i++ {} } // 無意義 func do2() { for i := 0; i < 200000000; i++ {} } func main() { go retrieveSomeThing() go doSomeThing() // 阻塞一下 time.Sleep(3 * time.Second) }
解決問題前,先介紹下 pprof。
pprof
pprof 包會輸出運行時的分析數據(profiling data),這些數據可以被 pprof 的可視化工具解析。Go 標準庫主要提供了兩個包:
-
runtime/pprof
通過寫入到文件的方式暴露 profile 數據; -
net/http/pprof
通過 http 服務暴露 profile 數據,適用于守護進程。
生成 profile 文件
CPU 性能分析
在 runtime/pprof
中,使用StartCPUProfile
開啟 CPU 性能分析。退出程序前,需要調用StopCPUProfile
把采樣數據 flush 到輸出文件。
采樣的頻率默認是 100 Hz(每秒 100 次)。
// 輸出到標準輸出,一般是指定文件 if err := pprof.StartCPUProfile(os.Stdout); err != nil { log.Fatal("could not start CPU profile: ", err) } defer pprof.StopCPUProfile()
內存性能分析
調用 WriteHeapProfile
開啟內存性能分析:
// 輸出到標準輸出,一般是指定文件 if err := pprof.WriteHeapProfile(os.Stdout); err != nil { log.Fatal("could not write memory profile: ", err) } }
分析 profile 文件 && 優化代碼
以開篇的代碼為例,由于是 CPU 過載,我們可以在 main 函數開啟 CPU Profile:
// 通過參數指定 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) } // 開啟 CPU 分析 if err := pprof.StartCPUProfile(f); err != nil { log.Fatal("could not start CPU profile: ", err) } defer pprof.StopCPUProfile() } // 業務代碼 go retrieveSomeThing() go doSomeThing() // 模擬阻塞 time.Sleep(5 * time.Second) }
我們執行命令,輸出 profile 文件到 cpu.prof。
go run main.go -cpuprofile cpu.prof
go tool pprof
Go 提供性能解析工具:go tool pprof
。我們使用 go tool 打開 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)
這是個交互式的界面,輸入help
可以查看所有命令。
top 命令
我們使用 topN 命令,查看根據 flat 從大到小排序的前 N 條數據。
(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 命令返回數據有5個指標:
- flat : 本函數占用的 CPU 時間,不包括調用函數的時間;
- flat% : flat 占的百分比;
- sum% : 前面 flat% 的總和;
- cum : 累計時間,包括調用的函數的時間;
- cum% : cum 的百分比。
以main.doSomeThing
(排第三的函數)為例子,耗時為:
func doSomeThing() { // flat: 80ms cum: 200ms do1() // 執行時間 50ms for i := 0; i < 200000000; i++ {} // 執行時間 80ms do2() // 執行時間 70ms }
doSomeThing
的 flat 的值為:
for i := 0; i < 200000000; i++ {}的執行時間(80ms),不包括do1和do2的時間。
doSomeThing
的 cum 的值為:
cum(200ms) = doSomething的flat(80ms) + do1的flat(50ms) + do2的flat(70ms)
ps: top 可以使用 -cum 參數來指定,根據 cum 排序。
list 命令
明白了 top 的指標的意思,我們關注到,排在 top1 的函數是 retrieveSomeThing。可以使用 list 命令,查看 retrieveSomeThing 耗時:
(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:// 處理某些業務,真實的代碼中這個死循環很隱蔽 . . 12:func retrieveSomeThing() { 4.21s 4.44s 13: for { . . 14: } . . 15:} . . 16: . . 17:// 處理其他的一些業務,無意義,用于后續做例子 . . 18:func doSomeThing() {
我們定位到13行需要優化。
總結
pprof 還有很多玩法,包括其他的性能指標,go tool 的其他命令,profile 文件的可視化等。這個留給讀者自行擴展閱讀。
本文主要參考了 Russ Cox 大神的文章:《Profiling Go Programs》 (go.dev/blog/pprof)… 文章為反駁 "Go性能不如其他語言"的觀點,借助 pprof 大幅度優化了程序的運行時間和內存。
原文鏈接:https://juejin.cn/post/7166518730514497543
相關推薦
- 2022-08-28 IntelliJ IDEA 下debugger熱加載(Hot Swap)有時候失效解決
- 2022-07-07 C++?opencv實現幾何圖形繪制_C 語言
- 2022-09-02 ahooks整體架構及React工具庫源碼解讀_React
- 2023-04-03 關于CUDA?out?of?memory的解決方案_python
- 2023-01-14 C#實現啟動項管理的示例代碼_C#教程
- 2022-04-08 swift表格控件使用方法詳解(UITableview)_Swift
- 2023-12-14 如何查看瀏覽器內核版本
- 2023-01-02 Python中字符串的常用方法總結_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支