[Go] pprof 效能分析

程式語言:Go
Package:
runtime/pprof
net/http/pprof
官方文件:Profiling Go Programs
官方文件:net/http/pprof
官方文件:runtime/pprof
官方文件:runtime
官方文件:runtime/debug

功能:檢查性能瓶頸

主要檢測功能

  • CPU Profiling
    • CPU 分析,按照一定的頻率採集所監聽的應用程序 CPU(含寄存器)的使用情況
      可確定應用程序在主動消耗 CPU 週期時花費時間的位置
  • Memory Profiling
    • Heap 分析,包含每个 goroutine 分配大小,分配堆栈等
  • Block Profiling
    • 阻塞分析,記錄 goroutine 阻塞等待同步(包括定時器通道)的位置
  • Mutex Profiling
    • 互斥鎖分析,報告互斥鎖的競爭情況

go test

  • 透過 "go test -bench ." 生成相關訊息
    go test -bench . -cpuprofile cpu.prof -memprofile mem.prof -blockprofile block.prof -mutexprofile mutex.prof
  • 查詢指令用法
    go test -h
  • 使用內建 go tool 觀看數據
    go tool pprof FileName
    top # 顯示前幾名
    web # 用瀏覽器觀看圖,需安裝 Graphviz
  • 安裝原生 PProf 工具觀看數據,具有 Flame Graph
    go get -u github.com/google/pprof # 安裝方法
    pprof -http=:8080 FileName

runtime/pprof

  • 透過 "StartCPUProfile", "WriteHeapProfile" 等... 生成相關訊息
    若程式太過簡單,則無法顯示任何訊息,因 StartCPUProfile 的預設偵測頻率為 500Hz
  • 使用內建 go tool 觀看數據
    go tool pprof FileName
    top # 顯示前幾名
    web # 用瀏覽器觀看圖,需安裝 Graphviz
  • 安裝原生 PProf 工具觀看數據,具有 Flame Graph
    go get -u github.com/google/pprof # 安裝方法
    pprof -http=:8080 FileName

net/http/pprof

  • 透過 http 服務得到 Profile 採樣文檔,可使用在 server 上
  • 查看數據
    URI/debug/pprof/
  • 使用內建 go tool 觀看數據
    go tool pprof URI/debug/pprof/heap
    go tool pprof URI/debug/pprof/profile?seconds=30
  • 安裝原生 PProf 工具觀看數據,具有 Flame Graph
    go get -u github.com/google/pprof # 安裝方法
    pprof -http=:8080 URI/debug/pprof/heap
    pprof -http=:8080 URI/debug/pprof/profile?seconds=1

測試程式

main.go
  1. package main
  2.  
  3. import (
  4. "log"
  5. "net/http"
  6. "os"
  7. "runtime"
  8. "runtime/pprof"
  9. "sync"
  10. "time"
  11. )
  12.  
  13. var mutex sync.Mutex
  14.  
  15. func main() {
  16. // 需設定,不然 block profile 不會有值
  17. runtime.SetBlockProfileRate(1)
  18. // 需設定,不然 mutex profile 不會有值
  19. runtime.SetMutexProfileFraction(1)
  20.  
  21. startPProfCPU()
  22. fabonacci(10000000)
  23. stopPProfCPU()
  24.  
  25. mutex.Lock()
  26. go test()
  27. time.Sleep(2 * time.Millisecond)
  28. mutex.Unlock()
  29.  
  30. pprofBlock()
  31. pprofMutex()
  32. pprofMem()
  33.  
  34. log.Println(http.ListenAndServe("localhost:6060", nil))
  35. }
  36.  
  37. func fabonacci(index int) int {
  38. if index <= 0 {
  39. log.Fatal("index is less than 1")
  40. }
  41.  
  42. pre0 := 0
  43. pre1 := 1
  44. for i := 1; i < index; i++ {
  45. pre1, pre0 = pre0+pre1, pre1
  46. }
  47. return pre1
  48. }
  49.  
  50. func test() {
  51. go func() {
  52. m := make(map[int]int)
  53. for i := 0; i < 1000000; i++ {
  54. m[i] = i
  55. }
  56. }()
  57. mutex.Lock()
  58. time.Sleep(1 * time.Second)
  59. mutex.Unlock()
  60. }
  61.  
  62. func startPProfCPU() {
  63. cpuprofile := "cpu.out"
  64. f, err := os.Create(cpuprofile)
  65. if err != nil {
  66. log.Fatal("could not create CPU profile: ", err)
  67. }
  68. if err := pprof.StartCPUProfile(f); err != nil {
  69. log.Fatal("could not start CPU profile: ", err)
  70. }
  71. }
  72.  
  73. func stopPProfCPU() {
  74. pprof.StopCPUProfile()
  75. }
  76.  
  77. func pprofMem() {
  78. memprofile := "mem.out"
  79. f, err := os.Create(memprofile)
  80. if err != nil {
  81. log.Fatal("could not create memory profile: ", err)
  82. }
  83. defer f.Close()
  84.  
  85. runtime.GC() // get up-to-date statistics
  86. if err := pprof.WriteHeapProfile(f); err != nil {
  87. log.Fatal("could not write memory profile: ", err)
  88. }
  89. }
  90.  
  91. func pprofBlock() {
  92. blockprofile := "block.out"
  93. f, err := os.Create(blockprofile)
  94.  
  95. if err != nil {
  96. log.Fatal("could not create block profile: ", err)
  97. }
  98. defer f.Close()
  99.  
  100. p := pprof.Lookup("block")
  101. err = p.WriteTo(f, 0)
  102. if err != nil {
  103. log.Fatal("could not write write profile: ", err)
  104. }
  105. }
  106.  
  107. func pprofMutex() {
  108. mutexprofile := "mutex.out"
  109. f, err := os.Create(mutexprofile)
  110.  
  111. if err != nil {
  112. log.Fatal("could not create block profile: ", err)
  113. }
  114. defer f.Close()
  115.  
  116. p := pprof.Lookup("mutex")
  117. err = p.WriteTo(f, 0)
  118. if err != nil {
  119. log.Fatal("could not write write profile: ", err)
  120. }
  121. }

main_test.go
  1. package main
  2.  
  3. import (
  4. "testing"
  5. "time"
  6. )
  7.  
  8. func BenchmarkFabonacci(b *testing.B) {
  9. for i := 0; i < b.N; i++ {
  10. fabonacci(10)
  11.  
  12. mutex.Lock()
  13. go test()
  14. time.Sleep(2 * time.Millisecond)
  15. mutex.Unlock()
  16. }
  17. }

main_http.go
不能與 main.go 同個資料夾
  1. package main
  2.  
  3. import (
  4. "log"
  5. "net/http"
  6. _ "net/http/pprof"
  7. "sync"
  8. )
  9.  
  10. var mutex sync.Mutex
  11.  
  12. func main() {
  13. // 重覆運行,以便讓 http/pprof 抓取資料
  14. go func() {
  15. for {
  16. fabonacci(10000000)
  17. }
  18. }()
  19.  
  20. log.Fatal(http.ListenAndServe("localhost:6060", nil))
  21. }
  22.  
  23. func fabonacci(index int) int {
  24. if index <= 0 {
  25. log.Fatal("index is less than 1")
  26. }
  27.  
  28. pre0 := 0
  29. pre1 := 1
  30. for i := 1; i < index; i++ {
  31. pre1, pre0 = pre0+pre1, pre1
  32. }
  33. return pre1
  34. }

其他有用工具

  • race 檢測
    go run -race main.go
  • 用 panic 取代 return 觀察是否還有未關閉的 goroutine
    • 設定環境參數 GOTRACEBACK
      env GOTRACEBACK="all" go run main.go
    • 直接在程式碼中加入此行
      debug.SetTraceback("all")

參考

go pprof 性能分析
Golang 大杀器之性能剖析 PProf
golang 使用pprof和go-torch做性能分析
Go 语言运行时环境变量快速导览

留言