go 性能优化之 benchmark + pprof
testing 是go自带的一个轻量级的测试框架,主要有三个用途:单元测试(Test),基准测试(Benchmark)以及示例测试(Example)。
起因
写go也有几个月了,一直没太关注类似 benchmark
之类的性能分析工具,只知道埋头写业务代码。直到前几天,工作上的一个项目遇到了性能瓶颈,需要分析一下原因,就用到了 benchmark
。一顿分析,终于发现了程序中的”性能消耗大户“,颇有成就感。
测试case准备
平时写代码的过程中,会经常写一些 print
或者 debug
函数,谁又能想到,这些 print
偷偷摸摸的消耗了多少 CPU
和 内存资源呢?
AES对称加密是业务代码中经常会用到的一种加密方式,但是编码习惯如果不好,或者测试代码忘了删,就会导致加密性能急剧下降。
大师兄写的AES加密函数
1 |
|
二师兄写的AES加密函数
1 |
|
乍一看,大师兄和二师兄写的差不多,只是二师兄多了一个把加密结果写到日志中的操作。
就这么一点点的区别,性能能差多少呢?让我们把悬念留到最后。
benchmark 实施
假设当前项目中的代码就是二师兄写的,我们就来分析一下,当前的性能瓶颈到底在什么地方。
benchmark编写
写benchmark几个注意点:
- 文件名以
_test.go
结尾,如practice_test.go
- 函数名统一以
Benchmark
开头,参数是*testing.B
- 对于要测试的函数,函数外面套上一个 for 循环,for 循环次数的上限是
b.N
- 为了排除其它流程的干扰,一般会在 for 循环前加上
b.ResetTimer
对二师兄的加密函数写个基准测试
1 |
|
为了更加明显的对比,这里我们测试的加密数据的大小是512MB(即 1<< 29)
执行benchmark命令
testing 框架下的基准测试依赖
go test
工具
benchmark 命令示例
1 |
|
这个命令中的参数比较多,我们一个个的解释。
-bench
表示执行哪些基准测试函数,后面可以加需要执行的基准测试函数名称,也可以加.
,表示执行全部的基准测试函数。(其实-bench
后面可以加正则表达式)-run
表示执行哪些单元测试和示例测试函数,一般会加none,表示都不执行-benchmen
表示打印函数执行过程中的内存分配-cpuprofile
表示将全过程的CPU
的一些概要数据写到文件cpuprofile.out
中memprofile
表示将全过程的内存的一些概要数据写到文件memprofile.out
中
执行结果分析
1 |
|
从执行结果中能看到,for 循环每执行一次,耗时 8570217455 纳秒,同时会有55次内存分配操作,每次操作 6218811264 字节。
到这里,我们其实已经完成了基准测试的一个基本流程,也对二师兄的加密函数的性能和内存使用状况有了一个初步的认识。
但是,我们还是不知道性能瓶颈在哪!!
终极杀器:benchmark + pprof
pprof 是 go 自带的
CPU
分析器,常用来分析性能瓶颈。
在前面的基准测试中,我们生成了 CPU
概要文件 cpuprofile.out
以及内存概要文件 memprofile.out
,现在可以派上大用场了。
pprof 既可以通过命令行交互的方式查看CPU(内存)的概要数据,也可以通过web的方式查看直观的图形化展示。这里我们主要通过web的方式来展示。
当然,使用pprof工具前,你需要先安装 graphviz
,如果是mac,执行 brew install graphviz
就行。
pprof 分析 CPU
执行命令1
go tool pprof -http=":8081" cpuprofile.out
通过地址 http://localhost:8081/ui/
能看到
从这个截图中,我们很容易看到,加密部分总共耗时5.11s,完全用在加密上的耗时才0.76s,其它时间都是用在日志打印上和字符串转化上,
pprof 分析内存
1 |
|
通过地址 http://localhost:8081/ui/
能看到
从这个截图图中,我们很容易看到,5930.71MB的内存使用,真正用在加密上的才512MB以及对原始字符串padding操作的640MB,其它内存都耗费在字符串转化和各种 print
操作上。
很直观的,我们就知道了二师兄的代码问题就在那一段日志打印的操作上。
优化二师兄的代码
根据上面的分析,我们需要优化的就是日志打印的那部分代码。二师兄的代码优化后其实就是大师兄的代码。
我们把大师兄和二师兄的代码放一起跑一遍基准测试:1
go test -bench . -run none -benchmem -cpuprofile cpuprofile.out -memprofile memprofile.out
得到
1 |
|
从 CPU
耗时上看,大师兄的代码耗时只有二师兄的 1/7,单次内存消耗只有二师兄的 1/5,并且内存分配次数也只有二师兄的 1/4 左右。
结尾
二师兄在追赶大师兄的道路上,又前进了一大步,可喜可贺~~
参考
[2] go testing
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!