[译]Go性能分析工具工具和手段

翻译自 Basics of benchmarking, profiling and tracing with Go,作者对Go性能分析的工具和手段做了一个很好的总结。

这篇文档提供了Go提供的测量性能和收集运行时信息的工具的概览。它不是一个关于基准测试、性能分析和跟踪的详细教程。

所以这篇文档也可以看成是一篇备忘录。

在大多数情况下,您可以通过运行提供的示例源代码自行尝试。作为一种现场演示或研讨会,很容易使用这些工具进行试验和体验。

主要内容包括:

  • Benchmarking(基准测试):专注于一段特定的代码, 允许测量时间 和/或 内存信息。
  • Profiling(分析):在程序执行期间(或测试的时候)通过聚合采样收集的数据。分析是没有时间线的(和tracing不一样)。
  • Tracing(跟踪):通过程序执行期间(或测试的时候)收集发生的事件数据。跟踪是有时间线的。

基准测试:专注于一段特定的代码,允许测量时间和/或内存信息。
分析:在程序(或测试)执行期间通过采样收集的聚合数据。分析没有时间线。
跟踪:通过程序(或测试)执行期间发生的事件收集的数据。跟踪有时间线。

分析和跟踪技术可以应用于基准测试。

Go版本

测试代码通过 Go module提供,所以你不需要设置 GOPATH环境变量。

你需要支持 module 的 Go 版本, 代码例子使用 Go 1.12

CPU的测量

Go 提供的工具并不测量CPU的使用率,它只测量运行时间。 这一点很重要,不要把他们弄混。如果代码并不是CPU计算型的程序,这两者的数值并没有直接的关系。

例如,如果你在代码中增加time.Sleep(time.Millisecond), 这会改变输出,和CPU没有关系,但是结果会改变。

基准测试

可以通过Go测试工具进行基准测试。它相当简单,并且也有很好的相关文档。

基准测试的主要结果包括每次测试操作的指标:

  • 花费的时间。
  • 堆上分配的内存。
  • 分配的次数。

每个基准测试也可以作为分析或跟踪操作的起点。

请参阅“fibonacci/fibonacci_test.go”的代码,它是一个最简单的例子。

执行今准测试

运行测试 :

  • 不进行基准测试 : go test ./fibonacci
  • 进行基准测试 (time) : go test ./fibonacci -bench .
  • 进行基准测试 (time and memory) : go test ./fibonacci -bench . -benchmem

参数 -bench 后面的参数是一个正则表达式,所有匹配这个正则表达式的基准测试函数都会被执行。本例中的 . 并不是指当前文件夹,而是一个匹配所有测试的正则模式。 为了运行一个特定的基准测试,你可以使用正则表达式: -bench Suite (意味着 所有函数名包含Suite才对函数).

有用的提示: ResetTimer() 用来忽略测试中的setup花费, 还有StopTimer()StartTimer()方法: https://golang.org/pkg/testing/#B.ResetTimer

比较基准测试结果

使用外部工具可以比较两次的基准测试结果:

1
2
3
4
5
6
7
go get -u golang.org/x/tools/cmd/benchcmp
go test ./fibonacci -bench . -benchmem > old.txt
(do some changes in the code)
go test ./fibonacci -bench . -benchmem > new.txt
~/go/bin/benchcmp old.txt new.txt

分析

分析数据是通过采样和聚合搜集而来的,而不是详细的跟踪数据。CPU分析用来测量运行时间,内存分析测量堆分配(栈被忽略)。

CPU基准测试显示一个操作需要多长时间执行(全局视图),而分析则是记录函数的执行持续时间(详细视图)。你也可以分析内存消耗,同样可以可以获得相同的全局/详细视图。

分析基准测试

可以从基准测试中得到分析数据:

  • CPU分析使用 -cpuprofile=cpu.out
  • 内存分析使用 -benchmem -memprofile=mem.out

使用这两者的例子 :

1
2
3
4
5
go test ./fibonacci \
-bench BenchmarkSuite \
-benchmem \
-cpuprofile=cpu.out \
-memprofile=mem.out

CPU和内存的分析数据是分别存储的,可以独立地进行分析。

查看分析数据

有两种方法可以使用标准的GO工具来利用分析数据。

  • 使用命令行: go tool pprof cpu.out
  • 使用浏览器r : go tool pprof -http=localhost:8080 cpu.out

View菜单 :

  • Top :根据时间/内存花费排序的函数列表
  • Graph : f函数调用树,有时间/内存注解
  • Flamegraph : 火焰图,花费一目了然
  • 其它...

调用图和火焰图很相似,但有一个主要的区别:火焰图显示带有时间/内存数据的采样调用堆栈(它是一棵树),而图可以有多条路径聚合到同一个函数(它不是一棵树)。具有多条路径的函数时间/内存数据是多条路径花费的聚合值。两者都是有用的,你可以为你的情况选择合适的图。

在代码中开启分析

使用 pprof.StartCPUProfile(), pprof.StopCPUProfile()pprof.WriteHeapProfile()。查看 pprof包的文档可以获取更详细的信息。

一个例子 :

1
2
3
4
cd showfib
go build
./showfib 30 2>cpu.out
go tool pprof -http=localhost:8080 cpu.out

跟踪垃圾回收器(GC)

这部分不是那么有用,但是跟踪垃圾回收器的行为可以很容易的发现GC过高的压力。

在程序(或测试中)使用环境变量 : GODEBUG=gctrace=1

或者使用代码 :

1
2
3
4
"runtime/trace"
trace.Start(os.Stderr)
defer trace.Stop()

例子 webfib :

1
2
3
4
5
6
7
8
cd webfib
go build
GODEBUG=gctrace=1 ./webfib
(其它终端窗口中)
go get -u github.com/rakyll/hey
~/go/bin/hey -n 1000 http://localhost:8000/?n=30

跟踪

跟踪是在程序执行期间收集的事件。它们给出了程序执行的时间视图,其中包含有关堆、GC、Goroutines、core的使用等详细信息。

使用测试

测试时产生跟踪数据并可视化数据 :

1
2
3
4
5
go test ./fibonacci \
-bench BenchmarkSuite \
-trace=trace.out
go tool trace trace.out

警告 : Chrome 是唯一支持Go跟踪可视化的浏览器 !

可视化数据有很多有趣的细节... 文章 Go execution tracer 是一篇介绍跟踪GUI的教程.

提示 : 从View trace 部分点击问号 ? 可以查看帮助。

程序中开启跟踪

与使用测试进行跟踪类似,您必须添加代码以便将跟踪数据收集到文件中,然后使用go tool trace

1
2
3
4
"runtime/trace"
trace.Start(os.Stderr)
defer trace.Stop()

(没有例子, 查看 trace 包以获取更多信息)

跟踪和分析长时间运行的程序

Go工具开始真正闪耀!Go允许在执行期间分析运行HTTP服务的任何程序,甚至在生产环境中。这很容易。

数据只按需收集,不使用时不使用资源。

安装

引入 net/http/pprof 标准库,增加 handler 到 DefaultServeMux。 如果已经有一个http服务器已经使用了这个mux, 更简单了。

1
2
3
4
5
6
import _ "net/http/pprof"
// Add this only if needed
go func() {
log.Println(http.ListenAndServe("localhost:8000", nil))
}()

webfib例子使用了pprof。 可以实时访问 : http://localhost:8000/debug/pprof/ , 但是查看数据并不人性化。

安全

net/http/pprof提供的handler应该只被信任的客户端所访问。它不是您希望直接通过互联网或由不受信任的第三方在内部提供的内容。

net/http/pprof默认注册handler到DefaultServeMux。你应该使用独立的http server时(使用专一的Mux),使用独立的端口,不同的安全规则。

如果你使用第三方的router,你需要手工注册对应的handler到这些router中,有时候可能需要包装一下这些handler以满足这些router的要求。

分析

在使用net/http/pprof时也可以使用go tool pprof分析数据。

打开三个终端窗口 :

  • 运行程序
  • 收集跟踪数据 (可能会花费30s或者一两分钟)
  • 制造压力负载
1
2
3
4
5
6
7
cd webfib
go build
./webfib
go tool pprof -http=localhost:8080 http://localhost:8000/
~/go/bin/hey -n 2000 -c 200 http://localhost:8000/?n=30

go tool ... 命令行工具收据数据,然后打开一个浏览器,需要耐心等待,不会花费很长时间...

跟踪和分析

跟踪数据不得不手工收集,然后提供给go tool trace

使用三个终端窗口:

  • 运行程序
  • 收集跟踪数据 15秒
  • 制造压力负载
1
2
3
4
5
6
7
cd webfib
go build
./webfib
curl -o trace.out http://localhost:8000/debug/pprof/trace?seconds=15
~/go/bin/hey -n 2000 -c 200 http://localhost:8000/?n=30

可以使用浏览器查看跟踪数据 : go tool trace trace.out

用户定义的跟踪

Go 1.11 引入了自定义的跟踪。它并不是一个新的工具,而是一个在你的代码中增加事件到跟踪数据的方法。

这个方法包括 :

  • Task struct : 跟踪高级的操作
  • Region struct : 跟踪底层操作
  • Log function : 在跟踪数据中增加日志

anowebfib程序 (类似 另一个webfib程序) 使用了上面提到的所有方法。每一个HTTP请求使用Task标识,每一次调用fibonacci包使用Region记录, n记录在log中。

例如使用三个终端窗口进行跟踪 :

1
2
3
4
5
6
7
8
cd anowebfib
go build
./anowebfib
curl -o trace.out http://localhost:8000/debug/pprof/trace?seconds=20
~/go/bin/hey -n 2000 -c 200 http://localhost:8000/unique?n=30
~/go/bin/hey -n 2000 -c 200 http://localhost:8000/multiple?n=30

同样的运行: go tool trace trace.out

查看 :

  • 用户定义的任务。
  • 用户定义的区域。
  • 查看跟踪: goroutines中的事件。

有用的链接

包 :

一些文章 :