Go性能优化

测试代码: https://github.com/behappy-project/behappy-url-shortener

Go语言项目中的性能优化主要有以下几个方面:

  • CPU profile:报告程序的 CPU 使用情况,按照一定频率去采集应用程序在 CPU 和寄存器上面的数据
  • Memory Profile(Heap Profile):报告程序的内存使用情况
  • Block Profiling:报告 goroutines 不在运行状态的情况,可以用来分析和查找死锁等性能瓶颈
  • Goroutine Profiling:报告 goroutines 的使用情况,有哪些 goroutine,它们的调用关系是怎样的

采集性能数据

Go语言内置了获取程序的运行数据的工具,包括以下两个标准库:

  • runtime/pprof:采集工具型应用运行数据进行分析
  • net/http/pprof:采集服务型应用运行时数据进行分析

pprof开启后,每隔一段时间(10ms)就会收集下当前的堆栈信息,获取各个函数占用的CPU以及内存资源;最后通过对这些采样数据进行分析,形成一个性能分析报告。

只应该在性能测试的时候才在代码中引入pprof。

工具型应用

如果应用程序是运行一段时间就结束退出类型。那么最好的办法是在应用退出的时候把 profiling 的报告保存到文件中,进行分析。对于这种情况,可以使用runtime/pprof库。 首先在代码中导入runtime/pprof工具:

1
import "runtime/pprof"

CPU性能分析

开启CPU性能分析:

1
pprof.StartCPUProfile(w io.Writer)

停止CPU性能分析:

1
pprof.StopCPUProfile()

应用执行结束后,就会生成一个文件,保存了我们的 CPU profiling 数据。得到采样数据之后,使用go tool pprof工具进行CPU性能分析。

内存性能优化

记录程序的堆栈信息

1
pprof.WriteHeapProfile(w io.Writer)

得到采样数据之后,使用go tool pprof工具进行内存性能分析。

go tool pprof默认是使用-inuse_space进行统计,还可以使用-inuse-objects查看分配对象的数量。

服务型应用

如果应用程序是一直运行的,比如 web 应用,那么可以使用net/http/pprof库,它能够在提供 HTTP 服务进行分析。

如果使用了默认的http.DefaultServeMux(通常是代码直接使用 http.ListenAndServe(“0.0.0.0:8000”, nil)),只需要在你的web server端代码中按如下方式导入net/http/pprof

1
import _ "net/http/pprof"

如果使用自定义的 Mux,则需要手动注册一些路由规则:

1
2
3
4
5
r.HandleFunc("/debug/pprof/", pprof.Index)
r.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
r.HandleFunc("/debug/pprof/profile", pprof.Profile)
r.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
r.HandleFunc("/debug/pprof/trace", pprof.Trace)

如果使用的是gin框架,那么推荐使用github.com/gin-contrib/pprof,在代码中通过以下命令注册pprof相关路由。

1
pprof.Register(router)

不管哪种方式,HTTP 服务都会多出/debug/pprof endpoint,访问它会得到类似下面的内容:这个路径下还有几个子页面:

  • /debug/pprof/profile:访问这个链接会自动进行 CPU profiling,持续 30s,并生成一个文件供下载
  • /debug/pprof/heap: Memory Profiling 的路径,访问这个链接会得到一个内存 Profiling 结果的文件
  • /debug/pprof/block:block Profiling 的路径
  • /debug/pprof/goroutines:运行的 goroutines 列表,以及调用关系

这是一种非侵入式的指标采集,几乎不会影响服务的性能

说明其中几个endpoint

1、goroutine

点击goroutine 进入后显示当前函数栈:

2、heap

heap可以查看内存占用情况:http://127.0.0.1:xxxx/debug/pprof/heap?debug=1

看详细信息可以使用go tool pprof来进行分析:

1
2
3
4
5
6
# 查看内存占用数据
go tool pprof -inuse_space http://127.0.0.1:8080/debug/pprof/heap
go tool pprof -inuse_objects http://127.0.0.1:8080/debug/pprof/heap
# 查看临时内存分配数据
go tool pprof -alloc_space http://127.0.0.1:8080/debug/pprof/heap
go tool pprof -alloc_objects http://127.0.0.1:8080/debug/pprof/heap

一个函数alloc_space多不一定就代表它会导致进程的RSS高。

这个命令进入后,是一个交互式界面,输入top命令可以前10大的内存分配,flat是堆栈中当前层的inuse内存值,cum是堆栈中本层级的累计inuse内存值(包括调用的函数的inuse内存值,上面的层级)。

3、cpu

分析cpu时延:

  • top命令
1
2
3
4
5
flat: 当前函数占用CPU的时间
flat%: 当前函数占用CPU百分比
sum%: 当前所有函数累加使用 CPU 的比例
cum: 当前函数以及子函数占用CPU的时间
cum%: 当前函数以及子函数占用CPU的百分比
  • help有使用说明
  • 如果输入svg 可以生成图片:
  • 另外:我们输入gif,在当前目录生成一个gif图片。或者安装Graphviz后也可以输入pdf 生成pdf文件。

pprof交互命令行常用命令参数

1
2
3
4
5
top 10 列出前10
web 调用 graphviz 生成svg图片,然后打开
list 查看具体的函数分析
pdf 命令可以生成可视化的pdf文件
help 命令可以提供所有pprof支持的命令说明

go tool pprof命令

不管是工具型应用还是服务型应用,我们使用相应的pprof库获取数据之后,下一步的都要对这些数据进行分析,都可以使用go tool pprof命令行工具。

go tool pprof最简单的使用方式为:

1
go tool pprof [binary] [source]

或者

1
go tool pprof prof文件

其中:

  • binary 是应用的二进制文件,用来解析各种符号;
  • source 表示 profile 数据的来源,可以是本地的文件,也可以是 http 地址。

注意事项: 获取的 Profiling 数据是动态的,要想获得有效的数据,请保证应用处于较大的负载(比如正在生成中运行的服务,或者通过其他工具模拟访问压力)。否则如果应用处于空闲状态,得到的结果可能没有任何意义。

图形化和火焰图go-torch

先安装perl

图形化工具:

FlameGraph

go-torch

  • go get github.com/uber/go-torch
  • windows下使用需要对源码做更改, 把go-torch/render/flamegraph.go文件中的GenerateFlameGraph按如下方式修改,然后在go-torch目录下执行go install
1
2
3
4
5
6
7
8
9
10
11
// GenerateFlameGraph runs the flamegraph script to generate a flame graph SVG. 
func GenerateFlameGraph(graphInput []byte, args ...string) ([]byte, error) {
flameGraph := findInPath(flameGraphScripts)
if flameGraph == "" {
return nil, errNoPerlScript
}
if runtime.GOOS == "windows" {
return runScript("perl", append([]string{flameGraph}, args...), graphInput)
}
return runScript(flameGraph, args, graphInput)
}
  • 使用方式
1
2
3
4
5
go-torch -u http://127.0.0.1:8080 -t 30

-u –url:要访问的 URL,这里只是主机和端口部分
-s –suffix:pprof profile 的路径,默认为 /debug/pprof/profile
–seconds:要执行 profiling 的时间长度,默认为 30s
  • 火焰图查看方式
1
2
3
火焰图的y轴表示cpu调用方法的先后,
x轴表示在每个采样调用时间内,方法所占的时间百分比,越宽代表占据cpu时间越多。
通过火焰图我们就可以更清楚的找出耗时长的函数调用,然后不断的修正代码,重新采样,不断优化。
  • 此外还可以借助火焰图分析内存性能数据:
1
2
3
4
go-torch -inuse_space http://127.0.0.1:8080/debug/pprof/heap
go-torch -inuse_objects http://127.0.0.1:8080/debug/pprof/heap
go-torch -alloc_space http://127.0.0.1:8080/debug/pprof/heap
go-torch -alloc_objects http://127.0.0.1:8080/debug/pprof/heap

代码实验

  1. 我这里web框架使用的gin, 所以这里结合gin-contrib/pprof进行测试

下载依赖: go get github.com/gin-contrib/pprof

注册相关路由: pprof.Register(g)

  1. 在测试接口写一段问题代码
  1. 访问该测试接口

会导致select语句中的default没有内容, 上面的case v:=<-c:一直执行

进入调试, 共以下几个步骤

  • 调试, 查看cpu占用: go tool pprof http://localhost:3000/debug/pprof/profile
  • 使用top命令查看占用排名
  • 使用list方法查看该方法吃资源原因, 它会在具体问题代码处进行标记
  • 通过分析发现大部分CPU资源被15行占用, 我们在default分支添加一行time.Sleep(time.Second)即可
  • 输入web, 还可以进入一个dashboard进行更详尽的信息查看
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
PS D:\Project\study-go-project\behappy-url-shortener> go tool pprof http://localhost:3000/debug/pprof/profile
Fetching profile over HTTP from http://localhost:3000/debug/pprof/profile
Saved profile in C:\Users\94391\pprof\pprof.samples.cpu.009.pb.gz
Type: cpu
Time: Sep 11, 2022 at 3:03pm (CST)
Duration: 30.15s, Total samples = 57.20s (189.75%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top 3
Showing nodes accounting for 56.57s, 98.90% of 57.20s total
Dropped 43 nodes (cum <= 0.29s)
Showing top 3 nodes out of 16
flat flat% sum% cum cum%
37.75s 66.00% 66.00% 37.75s 66.00% runtime.chanrecv
13.63s 23.83% 89.83% 51.39s 89.84% runtime.selectnbrecv
5.19s 9.07% 98.90% 56.58s 98.92% behappy-url-shortener/src/handler.logicCode
(pprof) list behappy-url-shortener/src/handler.logicCode
Total: 57.20s
ROUTINE ======================== behappy-url-shortener/src/handler.logicCode in D:\Project\study-go-project\behappy-url-shortener\src\handler\shorten_handle.go
5.19s 56.58s (flat, cum) 98.92% of Total
. . 10:// 一段有问题的代码
. . 11:func logicCode() {
. . 12: var c chan int
. . 13: for {
. . 14: select {
5.19s 56.58s 15: case v := <-c:
. . 16: fmt.Printf("recv from chan, value:%v\n", v)
. . 17: default:
. . 18:
. . 19: }
. . 20: }

dashboard

1
2
PS D:\Project\study-go-project\behappy-url-shortener> go tool pprof -http localhost:3001 C:\Users\94391\pprof\pprof.samples.cpu.009.pb.gz                
Serving web UI on http://localhost:3001
  • graph, 这个图也可以在上一步输入svg/web进行导出svg,效果一样(如下图展示, 框越大, 则说明占用资源越多)
  • source,标记了各个占用资源代码
  • peek, 展示占用详尽情况
  • top等同于pprof中的top
  • 本质上dashboard就是交互命令行中的各个参数图形化了

speedscope UI

当然, 我们还可以用更加直观的方式分析,这里给大家推荐一个网站,https://www.speedscope.app/。 我们只需要将go tool生成的profile文件,上传到网站上:

image.png

就可以通过各种图形化的界面进行分析:

image.png

具体帮助可以查看:https://github.com/jlfwong/speedscope#views