本文章及以下示例均运行于windows环境
前言
开始进行benchmark性能测试之前,需要注意:
- 基准测试的代码文件必须以_test.go结尾
- 基准测试的函数必须以Benchmark开头,必须是可导出的
- 基准测试函数必须接受一个指向Benchmark类型的指针作为唯一参数
- 基准测试函数不能有返回值
- 最后的for循环很重要,被测试的代码要放到循环里
- b.N是基准测试框架提供的,表示循环的次数,因为需要反复调用测试的代码,才可以评估性能
简单使用
一个简单的benchtest用例
package main
import (
"testing"
)
func BenchmarkContactStr(b *testing.B) {
for i := 0; i < b.N; i++ {
str := "a"
str += "bc"
}
}
命令行执行:
go test -bench .
其中-bench .
则为参数,仅测试bench的函数(Benchmark开头),后面空格加一个.,则为测试全部
运行结果:
goos: windows
goarch: amd64
BenchmarkContactStr-12 34332700 31.6 ns/op
PASS
ok _/C_/Users/admin/Desktop/test 1.319s
其中,BenchmarkContactStr
为测试的函数名,函数名后的-12
为运行时对应的GOMAXPROCS
的值(逻辑CPU数量),34332700
表示测试循环的次数,ns/op
表示每一个操作耗费多少时间(纳秒)
基准测试框架对一个测试用例的默认测试时间是 1 秒。开始测试时,当以 Benchmark 开头的基准测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,同时以递增后的值重新调用基准测试用例函数。
常用命令行参数
-bench grep
:通过正则表达式过滤出需要进行benchtest的用例-count n
:跑n次benchmark,n默认为1-benchmem
:打印内存分配的信息-benchtime=5s
:自定义测试时间,默认为1s-run regexp
:只运行特定的测试函数,比如-run ABC
只测试函数名中包含ABC
的测试函数-timeout t
:测试时间如果超过t, panic,默认10分钟-v
:显示测试的详细信息,也会把Log、Logf方法的日志显示出来
查看benchtest的参数: go help testflag
查看内存分配
命令行执行:
go test -bench . -benchmem
运行结果:
goos: windows
goarch: amd64
BenchmarkContactStr-12 38319422 32.7 ns/op 3 B/op 1 allocs/op
PASS
ok _/C_/Users/admin/Desktop/test 2.471s
其中,BenchmarkContactStr
为测试的函数名,函数名后的-12
为运行时对应的GOMAXPROCS
的值(逻辑CPU数量),38319422
表示测试循环的次数,ns/op
表示每一个操作耗费多少时间(纳秒),B/op
表示每次调用需要分配的字节数量。allocs/op
表示每次调用有多少次分配
benchmark常用API
- b.StopTimer()
- b.StartTimer()
- b.ResetTimer()
- b.RunParallel(body func(*PB))
b.RunParallel
串行用法
func BenchmarkContactStr(b *testing.B) {
for i := 0; i < b.N; i++ {
str := "a"
str += "bc"
}
}
最基本用法,for循环中的执行过程在达到1秒或超过1秒时,总共执行多少次。b.N的值就是最大次数。
并行用法
func BenchmarkContactStr(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
str := "a"
str += "bc"
}
})
}
如果代码只是像上例这样写,那么并行的goroutine
个数是默认等于runtime.GOMAXPROCS(0)
创建P个goroutine
之后,再把b.N
打散到每个goroutine
上执行,所以并行用法就比较适合IO型
的测试对象
若想增大goroutine
的个数,那就使用b.SetParallelism(p int)
// 最终goroutine个数 = 形参p的值 * runtime.GOMAXPROCS(0)
numProcs := b.parallelism * runtime.GOMAXPROCS(0)
b.SetParallelism()
的调用一定要放在b.RunParallel()
之前
并行用法带来一些启示,注意到b.N
是被RunParallel()
接管的。意味着,开发者可以自己写一个RunParallel()
方法,goroutine
个数和b.N
的打散机制自己控制。或接管b.N
之后,定制自己的策略。
要注意b.N
会递增,这次b.N
执行完,不满足终止条件,就会递增b.N
,逼近上限,直至满足终止条件
// 终止策略: 执行过程中没有竟态问题 & 时间没超出 & 次数没达到上限
// d := b.benchTime
if !b.failed && b.duration < d && n < 1e9 {}
Start/Stop/ResetTimer
这三个都是对 计时统计器 和 内存统计器 操作。
benchmark中难免有一些初始化的工作,这些工作耗时不希望被计算进benchmark结果中
ResetTimer
// 串行情况在for循环之前调用
func BenchmarkContactStr(b *testing.B) {
// do something... 初始化
b.ResetTimer()
for i := 0; i < b.N; i++ {
str := "a"
str += "bc"
}
}
// 并行情况在b.RunParallel()之前调用
func BenchmarkContactStr(b *testing.B) {
// do something... 初始化
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
str := "a"
str += "bc"
}
})
}
Start/StopTimer
func BenchmarkContactStr(b *testing.B) {
// do something... 初始化
b.ResetTimer()
// do something... 某些过程需要计算性能
b.StopTimer()
// do something... 某些过程不需要计算性能
b.StartTimer()
for i := 0; i < b.N; i++ {
str := "a"
str += "bc"
}
}
关于更详尽的用法请查阅官方文档
结合 pprof性能监控
使用简单使用中的用例
同时查看内存与性能:
go test -bench . -benchmem -memprofile memprofile.out -cpuprofile profile.out
执行后会在当前目录生成memprofile.out
以及profile.out
2个文件
然后就可以用输出的文件使用pprof
查看性能
首先执行
go tool pprof profile.out
然后命令行输出:
Type: cpu
Time: Feb 28, 2020 at 4:01pm (CST)
Duration: 1.31s, Total samples = 1.32s (100.85%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
然后输入top
Type: cpu
Time: Feb 28, 2020 at 4:01pm (CST)
Duration: 1.31s, Total samples = 1.32s (100.85%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1140ms, 86.36% of 1320ms total
Showing top 10 nodes out of 61
flat flat% sum% cum cum%
360ms 27.27% 27.27% 1020ms 77.27% runtime.concatstrings
210ms 15.91% 43.18% 390ms 29.55% runtime.mallocgc
170ms 12.88% 56.06% 170ms 12.88% runtime.memmove
90ms 6.82% 62.88% 1110ms 84.09% runtime.concatstring2
70ms 5.30% 68.18% 490ms 37.12% runtime.rawstringtmp
60ms 4.55% 72.73% 60ms 4.55% runtime.releasem
60ms 4.55% 77.27% 60ms 4.55% runtime.stdcall1
50ms 3.79% 81.06% 50ms 3.79% runtime.acquirem
40ms 3.03% 84.09% 1150ms 87.12% _/C_/Users/admin/Desktop/test.BenchmarkContactStr
30ms 2.27% 86.36% 30ms 2.27% runtime.gomcache
可以看到cpu时间,可以继续执行list BenchmarkContactStr
(list + 测试函数名)
Type: cpu
Time: Feb 28, 2020 at 4:01pm (CST)
Duration: 1.31s, Total samples = 1.32s (100.85%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) list BenchmarkContactStr
Total: 1.32s
ROUTINE ======================== _/C_/Users/admin/Desktop/test.BenchmarkContactStr in C:\Users\admin\Desktop\test\benchmark_test.go
40ms 1.15s (flat, cum) 87.12% of Total
. . 3:import (
. . 4: "testing"
. . 5:)
. . 6:
. . 7:func BenchmarkContactStr(b *testing.B) {
10ms 10ms 8: for i := 0; i < b.N; i++ {
. . 9: str := "a"
30ms 1.14s 10: str += "bc"
. . 11: }
. . 12:}
以上则可以看到用例每一步执行的时间
查看内存占用
首先执行
go tool pprof memprofile.out
然后命令行输出:
Type: alloc_space
Time: Feb 28, 2020 at 4:01pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
然后输入top
Type: alloc_space
Time: Feb 28, 2020 at 4:01pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 121.38MB, 99.12% of 122.46MB total
Dropped 8 nodes (cum <= 0.61MB)
flat flat% sum% cum cum%
120.50MB 98.40% 98.40% 120.50MB 98.40% _/C_/Users/admin/Desktop/test.BenchmarkContactStr
0.88MB 0.72% 99.12% 1.45MB 1.18% compress/flate.NewWriter
0 0% 99.12% 1.95MB 1.60% compress/gzip.(*Writer).Write
0 0% 99.12% 1.95MB 1.60% runtime/pprof.(*profileBuilder).build
0 0% 99.12% 1.95MB 1.60% runtime/pprof.(*profileBuilder).flush
0 0% 99.12% 1.95MB 1.60% runtime/pprof.(*profileBuilder).locForPC
0 0% 99.12% 1.95MB 1.60% runtime/pprof.profileWriter
0 0% 99.12% 120.50MB 98.40% testing.(*B).launch
0 0% 99.12% 120.50MB 98.40% testing.(*B).runN
可以看到内存占用,可以继续执行list BenchmarkContactStr
(list + 测试函数名)
Type: alloc_space
Time: Feb 28, 2020 at 4:01pm (CST)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) list BenchmarkContactStr
Total: 122.46MB
ROUTINE ======================== _/C_/Users/admin/Desktop/test.BenchmarkContactStr in C:\Users\admin\Desktop\test\benchmark_test.go
120.50MB 120.50MB (flat, cum) 98.40% of Total
. . 5:)
. . 6:
. . 7:func BenchmarkContactStr(b *testing.B) {
. . 8: for i := 0; i < b.N; i++ {
. . 9: str := "a"
120.50MB 120.50MB 10: str += "bc"
. . 11: }
. . 12:}
以上则可以看到用例每一步的内存占用
测试注意和调优
- 避免频繁调用timer
- 避免测试数据过大
参考文档
go benchmark实践与原理
go benchmark 性能测试
Benchtest的简单使用
Go语言标准库文档中文版-testing
版权属于:谁把年华错落成诗 所有,转载请注明出处!
本文链接:https://blog.pomears.com/archives/56.html
如果博客部分链接出现404,请留言或者联系博主修复。