Go性能测试
在Go 单元测试中,只是验证了算法的正确性,对于算法的复杂度只是从一次结果判断,实际上,如果考虑多次的算法时间复杂度,看看算法的性能就需要使用Benchmarks。
Benchmarks的使用仍然需要在_test.go文件中,并且开头是Benchmark,形参必须是b *testing.B。
我们接着对Go 单元测试的例子中,Search_test.go文件添加如下函数
Search_test.go
func BenchmarkBinarySearch1(b *testing.B) {
arr:=make([]int,0)
for i:=0;i<100000;i++{
arr=append(arr,i)
}
for i:=0;i<b.N;i++{
BinarySearch1(arr,rand.Int()%100000)
}
}
func BenchmarkBinarySearch2(b *testing.B) {
arr:=make([]int,0)
for i:=0;i<100000;i++{
arr=append(arr,i)
}
for i:=0;i<b.N;i++{
BinarySearch2(arr,rand.Int()%100000)
}
}
运行时,需要加上-bench
基准函数会运行目标代码 b.N 次。在基准执行期间,会调整 b.N 直到基准测试函数持续足够长的时间。表明b.N是不确定的值,这是为了准确得到算法的性能。
对于足够长的时间,下面是指1s以上
b.N 从 1 开始,如果基准函数在 1 秒内就执行完了,那么 b.N 的值会递增以便基准函数再重新执行,即基准函数默认要运行 1 秒,如果该函数的执行时间在 1 秒内就运行完了,那么就递增 b.N 的值,重新再执行一次
命令
go test -bench=. -run=none
输出
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1-8 45808 34748 ns/op
BenchmarkBinarySearch2-8 92 14177941 ns/op
PASS
ok awesomeProject/Search 4.278s
解释
-bench=. :表示的是运行所有的基准测试,. 表示全部。
-run=none:表示过滤掉单元测试,不去跑UT的cases。
输出的结果内容分析:goos: windows:表示的是操作系统是windows。
goarch: amd64:表示目标平台的体系架构是amd64。
BenchmarkBinarySearch1-8:BenchmarkBinarySearch1表示运行的函数名称; 8表示的是,运行时对应的GOMAXPROCS的值,此数字默认为启动时 Go 进程可见的 CPU 数。。
45808 :表示的是b.N的在1s以上的值,循环执行了 45808 次。
34748ns/op:表示执行一次这个函数,消耗的时间是34748ns。
一些标志的解释
- -cpu可以定制cpu的数量
命令
go test -bench=. -cpu=1,2,4 -run=none
输出
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1 77134 16858 ns/op
BenchmarkBinarySearch1-2 67426 17360 ns/op
BenchmarkBinarySearch1-4 69294 16515 ns/op
BenchmarkBinarySearch2 247 5035322 ns/op
BenchmarkBinarySearch2-2 240 4969318 ns/op
BenchmarkBinarySearch2-4 235 5096158 ns/op
PASS
ok awesomeProject/Search 9.280s
不同CPU差别不大,因为没有涉及到并行吧
- -benchtime改变运行持续的时间,默认是大于1s,现在是大于10s
命令
go test -bench=. -benchtime=10s -run=none
-benchtime=10s:表示的是运行时间为10s,默认的时间是1s。
输出
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1-8 775342 18227 ns/op
BenchmarkBinarySearch2-8 1404 7309653 ns/op
PASS
ok awesomeProject/Search 25.515s
- 通过-count 标志,可以指定基准测试多跑几次
命令
go test -bench=. -benchtime=2s -count=5 -run=none
输出
C:\Users\WinterStar\go\src\awesomeProject\Search>go test -bench=. -benchtime=2s -count=5 -run=none
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1-8 160483 15417 ns/op
BenchmarkBinarySearch1-8 155282 14992 ns/op
BenchmarkBinarySearch1-8 149983 15191 ns/op
BenchmarkBinarySearch1-8 156448 14955 ns/op
BenchmarkBinarySearch1-8 161452 15440 ns/op
BenchmarkBinarySearch2-8 478 5082332 ns/op
BenchmarkBinarySearch2-8 492 4918752 ns/op
BenchmarkBinarySearch2-8 472 5147550 ns/op
BenchmarkBinarySearch2-8 459 5138692 ns/op
BenchmarkBinarySearch2-8 459 5126518 ns/op
PASS
ok awesomeProject/Search 27.379s
耗时管理
在启动或者循环过程中,存在耗时可能会影响函数的性能测试b.ResetTimer() 函数可以用于忽略启动的累积耗时
如果在每次循环迭代中,你有一些费时的配置逻辑,要使用 b.StopTimer() 和 b.StartTimer() 函数来暂定基准测试计时器。
func BenchmarkBinarySearch1(b *testing.B) {
arr:=make([]int,0)
for i:=0;i<100000;i++{
arr=append(arr,i)
}
b.ResetTimer()
for i:=0;i<b.N;i++{
b.StopTimer()
val:=rand.Int()%100000
b.StartTimer()
BinarySearch1(arr,val)
}
}
func BenchmarkBinarySearch2(b *testing.B) {
arr:=make([]int,0)
for i:=0;i<100000;i++{
arr=append(arr,i)
}
b.ResetTimer()
for i:=0;i<b.N;i++{
b.StopTimer()
val:=rand.Int()%100000
b.StartTimer()
BinarySearch2(arr,val)
}
}
命令
go test -bench=. -run=none
输出
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1-8 66144 17916 ns/op
BenchmarkBinarySearch2-8 226 4947265 ns/op
PASS
ok awesomeProject/Search 5.016s
可以看见时间变短了。
内存分配
内存分配的次数和分配的大小和基准测试的执行时间强相关。你可以通过在代码中增加 b.ReportAllocs() 函数来告诉 testing 框架记录内存分配的数据。
func BenchmarkBinarySearch1(b *testing.B) {
b.ReportAllocs()
arr:=make([]int,0)
for i:=0;i<100000;i++{
arr=append(arr,i)
}
b.ResetTimer()
for i:=0;i<b.N;i++{
b.StopTimer()
val:=rand.Int()%100000
b.StartTimer()
BinarySearch1(arr,val)
}
}
func BenchmarkBinarySearch2(b *testing.B) {
b.ReportAllocs()
arr:=make([]int,0)
for i:=0;i<100000;i++{
arr=append(arr,i)
}
b.ResetTimer()
for i:=0;i<b.N;i++{
b.StopTimer()
val:=rand.Int()%100000
b.StartTimer()
BinarySearch2(arr,val)
}
}
命令
go test -bench=. -run=none
输出
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1-8 67902 18285 ns/op 0 B/op 0 allocs/op
BenchmarkBinarySearch2-8 248 4713975 ns/op 56 B/op 2 allocs/op
PASS
ok awesomeProject/Search 5.098s
你也可以使用 go test -benchmem 标识来强制 testing 框架打印出所有基准测试的内存分配次数
-benchmem:表示显示memory的指标。
命令
go test -bench=. -benchmem -run=none
输出
goos: windows
goarch: amd64
pkg: awesomeProject/Search
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkBinarySearch1-8 50349 23428 ns/op 0 B/op 0 allocs/op
BenchmarkBinarySearch2-8 170 6705078 ns/op 56 B/op 2 allocs/op
PASS
ok awesomeProject/Search 5.316s
0 B/op:表示每次执行操作,分配0B的内存。
0 allocs/op:表示执行一次这个函数,分配内存的次数为1次。
收集基准测试数据
该 testing 包内置了对生成 CPU,内存和模块配置文件的支持。
- -cpuprofile=$FILE 收集 CPU 性能分析到 $FILE 文件
- -memprofile=$FILE,将内存性能分析写入到 $FILE 文件,-memprofilerate=N 调节采样频率为 1/N
- -blockprofile=$FILE,输出内部 goroutine 阻塞的性能分析文件数据到 $FILE
命令
go test -bench=. -benchmem -cpuprofile=c.p -run=none
上述命令会在当前目录下生成c.p的文件
读取文件命令
go tool pprof c.p
使用上述命令可以读取到c.p的内容,并使用protobuf交互
交互命令
top命令可以查看cpu使用情况的前几名
(pprof) top
Showing nodes accounting for 2440ms, 87.14% of 2800ms total
Dropped 45 nodes (cum <= 14ms)
Showing top 10 nodes out of 58
flat flat% sum% cum cum%
880ms 31.43% 31.43% 880ms 31.43% awesomeProject/Search.BinarySearch1
720ms 25.71% 57.14% 720ms 25.71% awesomeProject/Search.BinarySearch2.func1
400ms 14.29% 71.43% 1080ms 38.57% sort.doPivot_func
180ms 6.43% 77.86% 180ms 6.43% runtime.stdcall1
90ms 3.21% 81.07% 90ms 3.21% runtime.stdcall6
40ms 1.43% 82.50% 40ms 1.43% runtime.(*mcache).releaseAll
40ms 1.43% 83.93% 140ms 5.00% sort.insertionSort_func
30ms 1.07% 85.00% 30ms 1.07% internal/reflectlite.Swapper.func5
30ms 1.07% 86.07% 140ms 5.00% runtime.readmemstats_m
30ms 1.07% 87.14% 30ms 1.07% runtime.updateTimerPMask
解释
最后一列为函数名称,其他各项内容意义如下(转载):
flat:当前函数占用CPU的耗时
flat%:当前函数占用CPU的耗时百分比
sum%:函数占用CPU的累积耗时百分比
cum:当前函数+调用当前函数的占用CPU总耗时
cum%: 当前函数+调用当前函数的占用CPU总耗时百分比
list 函数名 可以查看函数的cpu使用情况
(pprof) list List
Total: 2.80s
可以list后面跟具体的函数
(pprof) list awesomeProject/Search.BinarySearch1
Total: 2.80s
ROUTINE ======================== awesomeProject/Search.BinarySearch1 in C:\Users\WinterStar\go\src\awesomeProject\Search\Search.go
880ms 880ms (flat, cum) 31.43% of Total
. . 1:package Search
. . 2:
. . 3:import "sort"
. . 4:
. . 5:func BinarySearch1(arr []int,val int) bool{
430ms 430ms 6: for i:=0;i<len(arr);i++{
450ms 450ms 7: if arr[i]==val{
. . 8: return true
. . 9: }
. . 10: }
. . 11: return false
. . 12:}
输入web,会打开本地浏览器查看pprof的svg图来分析
注意:
这个web命令需要在生成的机器上安装graphviz
注意编译器的优化
可以看看这篇博客https://blog.csdn.net/qq_28119741/article/details/117935237
上面很多内容都参考该博客,非常感谢
本文链接:https://WinterStarHu.github.io/post/go-xing-neng-ce-shi/
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!