一、Golang性能分析工具

准备工作

1、安装graphviz
Go语言的内置工具可以借助graphviz软件将性能分析结果图形化。

OS X和Ubuntu可以直接使用brew和apt-get install graphviz
Windows用户可以下载zip包,然后将解压后的目录添加到Path环境变量中。
下载地址:http://www.graphviz.org/download/

2、将$GOPATH/bin目录添加到Path环境变量中

在代码中嵌入性能分析代码

这种方式可以很灵活的对任何代码片段进行性能分析。
示例代码(prof.go):

package main

import (
	"log"
	"math/rand"
	"os"
	"runtime"
	"runtime/pprof"
	"time"
)

const (
	col = 10000
	row = 10000
)

func fillMatrix(m *[row][col]int) {
	s := rand.New(rand.NewSource(time.Now().UnixNano()))

	for i := 0; i < row; i++ {
		for j := 0; j < col; j++ {
			m[i][j] = s.Intn(100000)
		}
	}
}

func calculate(m *[row][col]int) {
	for i := 0; i < row; i++ {
		tmp := 0
		for j := 0; j < col; j++ {
			tmp += m[i][j]
		}
	}
}

func main() {
	//创建输出文件
	f, err := os.Create("cpu.prof")
	if err != nil {
		log.Fatal("could not create CPU profile: ", err)
	}

	// 获取系统信息
	if err := pprof.StartCPUProfile(f); err != nil { //监控cpu
		log.Fatal("could not start CPU profile: ", err)
	}
	defer pprof.StopCPUProfile()

	// 主逻辑区,进行一些简单的代码运算
	x := [row][col]int{}
	fillMatrix(&x)
	calculate(&x)

	f1, err := os.Create("mem.prof")
	if err != nil {
		log.Fatal("could not create memory profile: ", err)
	}
	//runtime.GC()                                       // GC,获取最新的数据信息
	if err := pprof.WriteHeapProfile(f1); err != nil { // 写入内存信息
		log.Fatal("could not write memory profile: ", err)
	}
	f1.Close()

	f2, err := os.Create("goroutine.prof")
	if err != nil {
		log.Fatal("could not create groutine profile: ", err)
	}

	if gProf := pprof.Lookup("goroutine"); gProf == nil {
		log.Fatal("could not write groutine profile: ")
	} else {
		gProf.WriteTo(f2, 0)
	}
	f2.Close()

}

示例代码中只写了两个功能函数,分别是fillMatrix、calculate。
fillMatrix会使用随机数初始化传入的二维数组,calculate会计算二维数组每行元素的累加和。

在main函数中,通过StartCPUProfile和StopCPUProfile函数来生成程序运行时的CPU占用情况。可以通过控制两个函数的调用位置来生成特定代码片段的运行时CPU占用情况。

后面又调用了WriteHeapProfile函数来生成程序运行时的内存占用情况。

在程序末尾,通过Lookup和WriteTo函数生成了当前所在协程的profile文件。

编译并运行这段代码,会生成三个profile文件:

$ go build prof.go
$ ./prof.exe
$ ls
cpu.prof  goroutine.prof  mem.prof  prof.exe*  prof.go

使用go tool pprof分析cpu性能

$ go tool pprof cpu.prof
Type: cpu
Time: Feb 25, 2020 at 3:36pm (CST)
Duration: 2.12s, Total samples = 2s (94.37%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.98s, 99.00% of 2s total
Showing top 10 nodes out of 28
      flat  flat%   sum%        cum   cum%
     0.77s 38.50% 38.50%      1.09s 54.50%  math/rand.(*Rand).Int31n
     0.59s 29.50% 68.00%      1.93s 96.50%  main.fillMatrix
     0.25s 12.50% 80.50%      1.34s 67.00%  math/rand.(*Rand).Intn
     0.14s  7.00% 87.50%      0.14s  7.00%  math/rand.(*rngSource).Uint64
     0.08s  4.00% 91.50%      0.32s 16.00%  math/rand.(*Rand).Int31
     0.07s  3.50% 95.00%      0.21s 10.50%  math/rand.(*rngSource).Int63
     0.03s  1.50% 96.50%      0.03s  1.50%  main.calculate
     0.03s  1.50% 98.00%      0.24s 12.00%  math/rand.(*Rand).Int63
     0.01s   0.5% 98.50%      0.01s   0.5%  runtime.cgocall
     0.01s   0.5% 99.00%      0.01s   0.5%  runtime.globrunqget
(pprof) list main.fillMatrix
Total: 2s
ROUTINE ======================== main.fillMatrix in D:\project_root\gogogo\hello_test\src\pprof\prof.go
     590ms      1.93s (flat, cum) 96.50% of Total
         .          .     15:)
         .          .     16:
         .          .     17:func fillMatrix(m *[row][col]int) {
         .          .     18:   s := rand.New(rand.NewSource(time.Now().UnixNano()))
         .          .     19:
      10ms       10ms     20:   for i:=0; i<row; i++ {
      90ms       90ms     21:           for j:=0; j<col; j++ {
     490ms      1.83s     22:                   m[i][j] = s.Intn(100000)
         .          .     23:           }
         .          .     24:   }
         .          .     25:}
         .          .     26:
         .          .     27:func calculate(m *[row][col] int) {
(pprof) svg
Generating report in profile001.svg
(pprof) exit
$ ls
cpu.prof  goroutine.prof mem.prof  prof.exe*  prof.go  profile001.svg

上面的命令中,首先使用go tool pprof cpu.prof命令打开一个分析cpu.prof文件的交互式命令行,在交互命令行中使用top命令可以查看各个函数的cpu运行时间占比数据。

cum cum%代表函数以及函数调用其他函数的整体cpu运行时间和总体占比。
flat flat% 两列代表函数本身(不包含调用其他函数的耗时)运行的cpu运行时间和总体占比。

在命令输出中,我们可以看到:

     0.59s 29.50% 68.00%      1.93s 96.50%  main.fillMatrix

main.fillMatrix函数调用耗时较长,因为我们使用list main.fillMatrix命令来详细查看这个函数每一条语句的cpu耗时情况:

(pprof) list main.fillMatrix
Total: 2s
ROUTINE ======================== main.fillMatrix in D:\project_root\gogogo\hello_test\src\pprof\prof.go
     590ms      1.93s (flat, cum) 96.50% of Total
         .          .     15:)
         .          .     16:
         .          .     17:func fillMatrix(m *[row][col]int) {
         .          .     18:   s := rand.New(rand.NewSource(time.Now().UnixNano()))
         .          .     19:
      10ms       10ms     20:   for i:=0; i<row; i++ {
      90ms       90ms     21:           for j:=0; j<col; j++ {
     490ms      1.83s     22:                   m[i][j] = s.Intn(100000)
         .          .     23:           }
         .          .     24:   }
         .          .     25:}
         .          .     26:
         .          .     27:func calculate(m *[row][col] int) {

通过命令行输出,很容易发现main.fillMatrix函数调用耗费的大部分时间都花在了s.Intn(100000)语句上(随机数生成)。

交互式命令的最后,使用了svg命令,借助博客最前面安装的graphviz软件生成可视化的cpu耗时分析图,使用浏览器打开生成的svg文件,可以很直观的看到函数调用过程中的cpu耗时情况:
在这里插入图片描述
基本上,显示的方块越大,就代表cpu耗时越长。

也有人习惯使用go-torch工具通过火炬图的方式进行分析,只是展示方式有所不通,这里不做介绍。

使用go tool pprof分析mem性能

分析内存性能的pprof命令用法与分析cpu性能用法基本一致:

$ go tool pprof mem.prof
Type: inuse_space
Time: Feb 25, 2020 at 3:36pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 762.95MB, 99.85% of 764.10MB total
Dropped 1 node (cum <= 3.82MB)
      flat  flat%   sum%        cum   cum%
  762.95MB 99.85% 99.85%   764.10MB   100%  main.main
         0     0% 99.85%   764.10MB   100%  runtime.main
(pprof) list main.main
Total: 764.10MB
ROUTINE ======================== main.main in D:\project_root\gogogo\hello_test\src\pprof\prof.go
  762.95MB   764.10MB (flat, cum)   100% of Total
         .          .     37:   f, err := os.Create("cpu.prof")
         .          .     38:   if err != nil {
         .          .     39:           log.Fatal("could not create cpu profile: ", err)
         .          .     40:   }
         .          .     41:
         .     1.16MB     42:   if err := pprof.StartCPUProfile(f); err != nil {
         .          .     43:           log.Fatal("could not start cpu profile: ", err)
         .          .     44:   }
         .          .     45:
         .          .     46:   defer pprof.StopCPUProfile()
         .          .     47:
  762.95MB   762.95MB     48:   x := [row][col]int {}
         .          .     49:   fillMatrix(&x)
         .          .     50:   calculate(&x)
         .          .     51:
         .          .     52:   f1, err := os.Create("mem.prof")
         .          .     53:   if err != nil {
(pprof)

这里分析的是mem.prof文件,使用的命令同样是top和list。
通过top和list命令的执行结果,不难发现,程序内存分配大部分都集中在在48行的二维数组的初始化位置(x := [row][col]int {})。

在最前面的代码里有一条被注释掉了的runtime.GC() 调用,你可以尝试去除注释,然后重新生成mem.prof文件,再使用go tool pprof mem.prof命令进行分析,看看Go语言的GC有没有起作用。

使用go tool pprof分析goroutine(协程)性能

用法与前面分析cpu和mem用法一致。

通过http方式输出profile文件及性能分析

在Web应用场景中,程序大多通过http方式对外提供服务,Go语言中也提供了以http服务的方式输出profile的方法,获取到profile后,就可以通过pprof命令来进行分析。
需要在代码中添加的内容:

  1. 在代码中import _ “net/http/pprof”;
  2. 启动http server;
  3. 使用go tool pprof http:debuf/profile/profile?seconds=10(默认值30秒)打开交互命令行进行分析。

如果程序本身有http server,则第2步可以省略。

示例http server代码:

package main

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

func GetFibonacciSerie(n int) []int {
	ret := make([]int, 2, n)
	ret[0] = 1
	ret[1] = 1
	for i := 2; i < n; i++ {
		ret = append(ret, ret[i-2]+ret[i-1])
	}
	return ret
}

func index(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("Welcome!"))
}

func createFBS(w http.ResponseWriter, r *http.Request) {
	var fbs []int
	for i := 0; i < 1000000; i++ {
		fbs = GetFibonacciSerie(50)
	}
	w.Write([]byte(fmt.Sprintf("%v", fbs)))

}

func main() {
	http.HandleFunc("/", index)
	http.HandleFunc("/fb", createFBS)
	log.Fatal(http.ListenAndServe(":8081", nil))
}

代码中通过net/http对外暴露了两个http接口,分别是index和createFBS,createFBS通过http形式对外开放斐波那契数列的生成服务。
运行这个http server代码:

$ go run http.go

在浏览器中测试http server:
在这里插入图片描述运行go tool pprof命令分析性能:

$ go tool pprof http://127.0.0.1:8081/debug/pprof/profile?seconds=10
Fetching profile over HTTP from http://127.0.0.1:8081/debug/pprof/profile?seconds=10

在没有捕获到要分析的请求数据前,不会得到交互式命令行。
因此我们首先要在浏览器中调用待测试的服务接口:
在这里插入图片描述
可以多调用几次。。。
此时我们就可以获取到交互式命令行了:

$ go tool pprof http://127.0.0.1:8081/debug/pprof/profile?seconds=10
Fetching profile over HTTP from http://127.0.0.1:8081/debug/pprof/profile?seconds=10

Saved profile in C:\Users\wanhex\pprof\pprof.samples.cpu.004.pb.gz
Type: cpu
Time: Feb 25, 2020 at 4:52pm (CST)
Duration: 10.10s, Total samples = 430ms ( 4.26%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) (pprof)
(pprof) top -cum
Showing nodes accounting for 130ms, 30.23% of 430ms total
Showing top 10 nodes out of 78
      flat  flat%   sum%        cum   cum%
     100ms 23.26% 23.26%      220ms 51.16%  main.GetFibonacciSerie
         0     0% 23.26%      220ms 51.16%  main.createFBS
         0     0% 23.26%      220ms 51.16%  net/http.(*ServeMux).ServeHTTP
         0     0% 23.26%      220ms 51.16%  net/http.(*conn).serve
         0     0% 23.26%      220ms 51.16%  net/http.HandlerFunc.ServeHTTP
         0     0% 23.26%      220ms 51.16%  net/http.serverHandler.ServeHTTP
         0     0% 23.26%      190ms 44.19%  runtime.systemstack
      10ms  2.33% 25.58%      120ms 27.91%  runtime.makeslice
      10ms  2.33% 27.91%      110ms 25.58%  runtime.mallocgc
      10ms  2.33% 30.23%       80ms 18.60%  runtime.(*mcache).nextFree
(pprof) list main.createFBS
Total: 430ms
ROUTINE ======================== main.createFBS in D:\project_root\gogogo\hello_test\src\pprof\http.go
         0      220ms (flat, cum) 51.16% of Total
         .          .     22:}
         .          .     23:
         .          .     24:func createFBS(w http.ResponseWriter, r *http.Request) {
         .          .     25:   var fbs []int
         .          .     26:   for i := 0; i < 1000000; i++ {
         .      220ms     27:           fbs = GetFibonacciSerie(50)
         .          .     28:   }
         .          .     29:   w.Write([]byte(fmt.Sprintf("%v", fbs)))
         .          .     30:
         .          .     31:}
         .          .     32:
(pprof) list main.GetFibonacciSerie
Total: 430ms
ROUTINE ======================== main.GetFibonacciSerie in D:\project_root\gogogo\hello_test\src\pprof\http.go
     100ms      220ms (flat, cum) 51.16% of Total
         .          .      6:   "net/http"
         .          .      7:   _ "net/http/pprof"
         .          .      8:)
         .          .      9:
         .          .     10:func GetFibonacciSerie(n int) []int {
         .      120ms     11:   ret := make([]int, 2, n)
         .          .     12:   ret[0] = 1
      10ms       10ms     13:   ret[1] = 1
      20ms       20ms     14:   for i := 2; i < n; i++ {
      60ms       60ms     15:           ret = append(ret, ret[i-2]+ret[i-1])
         .          .     16:   }
      10ms       10ms     17:   return ret
         .          .     18:}
         .          .     19:
         .          .     20:func index(w http.ResponseWriter, r *http.Request) {
         .          .     21:   w.Write([]byte("Welcome!"))
         .          .     22:}
(pprof)

这里我们同样使用top和list命令来分析cpu性能。
此处的top命令使用-cum参数来对函数调用时累计cpu耗时进行排序后再输出。

测试用例代码中生成profile及性能分析

如果调用功能函数的代码是测试用例代码,则可以以非侵入的方式获取到profile文件。
具体获取profile文件及性能分析方法,可参见博客《二、Golang性能调优示例》

注:

博客内容为极客时间视频课《Go语言从入门到实战》学习笔记。
参考课程链接:
https://time.geekbang.org/course/intro/160?code=NHxez89MnqwIfa%2FvqTiTIaYof1kxYhaEs6o2kf3ZxhU%3D&utm_term=SPoster
博客参考代码:
https://github.com/geektime-geekbang/go_learning/tree/master/code/ch46
若访问github较慢,可使用加速器:
http://91tianlu.date/aff.php?aff=3468

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值