[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
package main

import (
    "log"
    "net/http"
    "os"
    "runtime"
    "runtime/pprof"
    "sync"
    "time"
)

var mutex sync.Mutex

func main() {
    // 需設定,不然 block profile 不會有值
    runtime.SetBlockProfileRate(1)
    // 需設定,不然 mutex profile 不會有值
    runtime.SetMutexProfileFraction(1)

    startPProfCPU()
    fabonacci(10000000)
    stopPProfCPU()

    mutex.Lock()
    go test()
    time.Sleep(2 * time.Millisecond)
    mutex.Unlock()

    pprofBlock()
    pprofMutex()
    pprofMem()

    log.Println(http.ListenAndServe("localhost:6060", nil))
}

func fabonacci(index int) int {
    if index <= 0 {
        log.Fatal("index is less than 1")
    }

    pre0 := 0
    pre1 := 1
    for i := 1; i < index; i++ {
        pre1, pre0 = pre0+pre1, pre1
    }
    return pre1
}

func test() {
    go func() {
        m := make(map[int]int)
        for i := 0; i < 1000000; i++ {
            m[i] = i
        }
    }()
    mutex.Lock()
    time.Sleep(1 * time.Second)
    mutex.Unlock()
}

func startPProfCPU() {
    cpuprofile := "cpu.out"
    f, err := os.Create(cpuprofile)
    if err != nil {
        log.Fatal("could not create CPU profile: ", err)
    }
    if err := pprof.StartCPUProfile(f); err != nil {
        log.Fatal("could not start CPU profile: ", err)
    }
}

func stopPProfCPU() {
    pprof.StopCPUProfile()
}

func pprofMem() {
    memprofile := "mem.out"
    f, err := os.Create(memprofile)
    if err != nil {
        log.Fatal("could not create memory profile: ", err)
    }
    defer f.Close()

    runtime.GC() // get up-to-date statistics
    if err := pprof.WriteHeapProfile(f); err != nil {
        log.Fatal("could not write memory profile: ", err)
    }
}

func pprofBlock() {
    blockprofile := "block.out"
    f, err := os.Create(blockprofile)

    if err != nil {
        log.Fatal("could not create block profile: ", err)
    }
    defer f.Close()

    p := pprof.Lookup("block")
    err = p.WriteTo(f, 0)
    if err != nil {
        log.Fatal("could not write write profile: ", err)
    }
}

func pprofMutex() {
    mutexprofile := "mutex.out"
    f, err := os.Create(mutexprofile)

    if err != nil {
        log.Fatal("could not create block profile: ", err)
    }
    defer f.Close()

    p := pprof.Lookup("mutex")
    err = p.WriteTo(f, 0)
    if err != nil {
        log.Fatal("could not write write profile: ", err)
    }
}

main_test.go
package main

import (
    "testing"
    "time"
)

func BenchmarkFabonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        fabonacci(10)

        mutex.Lock()
        go test()
        time.Sleep(2 * time.Millisecond)
        mutex.Unlock()
    }
}

main_http.go
不能與 main.go 同個資料夾
package main

import (
    "log"
    "net/http"
    _ "net/http/pprof"
    "sync"
)

var mutex sync.Mutex

func main() {
    // 重覆運行,以便讓 http/pprof 抓取資料
    go func() {
        for {
            fabonacci(10000000)
        }
    }()

    log.Fatal(http.ListenAndServe("localhost:6060", nil))
}

func fabonacci(index int) int {
    if index <= 0 {
        log.Fatal("index is less than 1")
    }

    pre0 := 0
    pre1 := 1
    for i := 1; i < index; i++ {
        pre1, pre0 = pre0+pre1, pre1
    }
    return pre1
}

其他有用工具

  • 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 语言运行时环境变量快速导览

留言