Go pprof性能分析工具

738 阅读3分钟

pprof是GoLang提供的程序性能分析工具,prof是profile(画像)的缩写,我们可以用pprof来分析

  • CPU Profiling:CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置
  • Memory Profiling:内存分析,在应用程序进行堆分配时记录堆栈跟踪,用于监视当前和历史内存使用情况,以及检查内存泄漏
  • Block Profiling:阻塞分析,记录 goroutine 阻塞等待同步(包括定时器通道)的位置

利用pprof进行性能分析主要有三种方式:

  • 利用基准测试benchmark采集数据+pprof 分析
  • 工具类应用(利用runtime/pprof库)将数据写入prof文件+pprof分析
  • 服务性应用("net/http/pprof"),通过http服务器定时采集数据+web pprof 在线分析

Benchmark + pprof

利用基准测试可以在开发过程中测试自己代码的性能,分析出耗时的瓶颈在哪。

func BenchmarkStr(b *testing.B) {
   for i := 0; i < b.N; i++ {
      s := ""
      for i := 0; i < 100000; i++ {
         s += strconv.Itoa(i)
      }
   }

}

func BenchmarkStr2(b *testing.B) {
   for i := 0; i < b.N; i++ {
      buf := bytes.Buffer{}
      for i := 0; i < 10000; i++ {
         buf.WriteString(strconv.Itoa(i))
      }
   }

}

例如写两个基准测试测试两种字符串拼接的性能

执行测试

go test -bench=. -cpuprofile=cpu.prof -benchmem -v

-cpuprofile=cpu.prof 是生成cpu分析画像(还有-memprofile=mem.pprof,-blockprofile=block.pprof)

-benchmem 是打印基准测试的内存分配情况 image.png

可以看到在8核的机器上,每次执行第一个字符串拼接方式的耗时和内存分配次数比第二种高的多

然后通过pprof分析具体耗时的地方

go tool pprof cpu.prof 

image.png

image.png

image.png

从图上可以对比出两种拼接方式的性能差异(随便写的代码,不太严谨,可以通过这种方式去找到代码的性能瓶颈),通过火焰图看更直观。

go tool pprof -http=":8080" cpu.prof

利用这个工具可以在webUi更清晰的看到分析结果

mac需要安装(brew install graphviz)

image.png

还可以生成火焰图

image.png 需要的数据应该都能在这上面找到

工具类应用

对于一些工具类应用,我们会定时去执行它,那么可以利用runtime/pprof库来获取数据,生成画像文件

package main

import (
	"fmt"
	"os"
	"runtime/pprof"
)

func main() {
	//CPU Profile
	f, err := os.Create("./cpuprofile")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()
	pprof.StartCPUProfile(f)
	defer pprof.StopCPUProfile()

	//Memory Profile
	fm, err := os.Create("./memoryprofile")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer fm.Close()
	pprof.WriteHeapProfile(fm)

	for i := 0; i < 100; i++ {
		fmt.Println("tttttttttt")
	}
}

运行代码则会生成cpu和内存的profile文件(保存运行时的环境数据),分析方式和上面一样。

服务类应用

对于一些持续运行的服务,不能要求需要分析的时候就去执行一下,可以利用 _ "net/http/pprof",定时去采集运行环境数据,通过http暴露出来。

package main

import (
   "fmt"
   "net/http"
   _ "net/http/pprof"
   "strings"
)

func sayhelloName(w http.ResponseWriter, r *http.Request) {
   r.ParseForm()       //解析参数,默认是不会解析的
   fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
   fmt.Println("path", r.URL.Path)
   fmt.Println("scheme", r.URL.Scheme)
   fmt.Println(r.Form["url_long"])
   for k, v := range r.Form {
      fmt.Println("key:", k)
      fmt.Println("val:", strings.Join(v, ""))
   }
   fmt.Fprintf(w, "Hello ttttt")
}

func main() {
   http.HandleFunc("/", sayhelloName)       //设置访问的路由
   err := http.ListenAndServe(":9090", nil) //设置监听的端口
   if err != nil {
      fmt.Printf("ListenAndServe: %s", err)
   }
}

如果是gin框架,则引入

"github.com/gin-contrib/pprof"

r := gin.New()
pprof.Register(r)

运行程序

访问 http://localhost:9090/debug/pprof/ 就能看到采集的数据

默认的采样数据应该是60s一次,需要等一会才能下载文件

分析方式和前面说的一样

可以将profile下载到本地,也可以直接

go tool pprof http://localhost:9090/debug/pprof/profile

image.png

也可以通过命令行拉取和查看内存信息

 go tool pprof -http=:8888 http://localhost:9090/debug/pprof/heap

Reference

www.topgoer.com/%E5%85%B6%E…

zhuanlan.zhihu.com/p/259689535…