go 语言 基准测试 结果解读
基准测试
是测量一个程序在固定工作负载下的性能,使用 -bench
标记可以对代码进行基准测试
go test -v -bench=. -run=^$ gott/hello
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-4 4964053 228.5 ns/op
PASS
ok gott/hello 2.386s
.
表示执行代码包中所有的基准测试用例(前缀为Benchmark
的方法),由于功能测试也会被运行,但同时运行功能测试会影响性能测试的结果,通过加入-run=^$
,来禁止功能测试与性能测试一起执行。^$
意味着:只执行名称为空的功能测试函数,即不执行任何测试函数。指定运行某个基准测试
go test -v -bench=BenchmarkHello gott/hello
功能测试也会一起运行。。。。。。
b.N
值的确定
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
hello("Max")
}
}
当测试开始时,b.N
的值被设置为1,执行后如果没有超过默认执行时间上限(默认为1秒),则加大b.N
的值,按某种规则一直递增,直到执行时间等于或超过上限,那么就用这一次的b.N
的值,做为测试的最终结果
BenchmarkHello-4 4964053 228.5 ns/op
PASS
ok gott/hello 2.386s
BenchmarkHello-4
表示执行 BenchmarkHello 时,所用的最大P的数量为44964053
: 表示hello()
方法在达到这个执行次数时,等于或超过了1秒228.5 ns/op
: 表示每次执行hello()
所消耗的平均执行时间2.386s
:表示测试总共用时
测试总时间的计算
既然4964053
表示1秒或大于1秒时执行的次数,那么测试总时间用时却是2.386s
,超出了不少,这是为什么呢
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
hello("Max")
}
b.Log("NNNNN:", b.N)
}
在测试中加入b.Log("NNNNN:", b.N)
,再执行基准测试,并加入-v
,打印测试中的日志
go test -v -bench=. -run=^$ gott/hello
=== RUN TestHello
hello_test.go:15: hello Max
--- PASS: TestHello (0.00s)
=== RUN TestPrint
hello_test.go:19: just Print
--- PASS: TestPrint (0.00s)
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello
hello_test.go:26: NNNNN: 1
hello_test.go:26: NNNNN: 100
hello_test.go:26: NNNNN: 10000
hello_test.go:26: NNNNN: 1000000
hello_test.go:26: NNNNN: 3541896
hello_test.go:26: NNNNN: 4832275
BenchmarkHello-4 4832275 236.8 ns/op
PASS
ok gott/hello 2.395s
可以看到b.Log("NNNNN:", b.N)
被执行了6次,这证明了之前提到的,测试会对b.N
依次递增,直到执行时间等于或超过上限。在对BenchmarkHello()
运行基准测试时,N值依次按1,100,10000,1000000,3541896,4832275
递增,直到执行次数为4832275
时,执行时间等于或超过了上限。
同时也说明BenchmarkHello()
一共被调用了6次,每次运行BenchmarkHello()
都要消耗一定的时间,所以测试总耗时为这6次调用时间之和,2.395s
,超过了1秒
平均执行时间的计算
应该用,运行4832275
时所消耗的时间 t
,t / 4832275 = 236.8 ns/op
- 如果用 测试总共用时 / 最多可以执行的次数 则不等于 平均执行时间,即
2.386 * (1000 ** 3) / 4832275 = 493.7
大于测试结果中的236.8 ns/op
。 - 如果用
1 秒 / 4832275 = 206 ns
,与236.8 ns/op
并不是很接近 - 如果把尝试过程中的运行次数也加入进来
total = 1 + 100 + 10000 + 1000000 + 3541896 + 4832275
,即2.386 * (1000 ** 3) / 9384272 = 254.25
与236.8 ns/op
接近 - 反推运行时间:
4832275 * 236.8 ns / 1000 ** 3 = 1.14s
,测试结果使用了运行时间超过1秒上限时的数值
问题:是否测试总时间一定会超过1秒?答:因为要找到最大可执行次数,而在这之前肯定要进行多次尝试,所以测试总时间应该总是会超过1秒的。
benchtime 标记
可以通过 -benchtime
标记修改默认时间上限,比如改为3秒
go test -v -bench=. -benchtime=3s -run=^$ gott/hello
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello
hello_test.go:31: NNNNN: 1
hello_test.go:32: /Users/ga/m/opt/go/go_root
hello_test.go:31: NNNNN: 100
hello_test.go:32: /Users/ga/m/opt/go/go_root
hello_test.go:31: NNNNN: 10000
hello_test.go:32: /Users/ga/m/opt/go/go_root
hello_test.go:31: NNNNN: 1000000
hello_test.go:32: /Users/ga/m/opt/go/go_root
hello_test.go:31: NNNNN: 15927812
hello_test.go:32: /Users/ga/m/opt/go/go_root
BenchmarkHello-4 15927812 223.4 ns/op
PASS
ok gott/hello 3.802s
还可以设置具体的探索次数最大值,格式为-benchtime=Nx
go test gott/hello -run=^$ -bench=BenchmarkHello -benchtime=50x
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-4 50 2183 ns/op
--- BENCH: BenchmarkHello-4
hello_test.go:35: NNNNN: 1
hello_test.go:36: /Users/ga/m/opt/go/go_root
hello_test.go:35: NNNNN: 50
hello_test.go:36: /Users/ga/m/opt/go/go_root
PASS
ok gott/hello 0.011s
b.N
的值被设置为50,函数运行了50次
benchmem 标记
可以通过-benchmem
标记查看内存使用信息
go test gott/hello -run=^$ -bench=BenchmarkHello -benchmem
goos: darwin
goarch: amd64
pkg: gott/hello
BenchmarkHello-4 5137456 223.1 ns/op 32 B/op 2 allocs/op
--- BENCH: BenchmarkHello-4
hello_test.go:35: NNNNN: 1
hello_test.go:36: /Users/ga/m/opt/go/go_root
hello_test.go:35: NNNNN: 100
hello_test.go:36: /Users/ga/m/opt/go/go_root
hello_test.go:35: NNNNN: 10000
hello_test.go:36: /Users/ga/m/opt/go/go_root
hello_test.go:35: NNNNN: 1000000
hello_test.go:36: /Users/ga/m/opt/go/go_root
hello_test.go:35: NNNNN: 5137456
hello_test.go:36: /Users/ga/m/opt/go/go_root
... [output truncated]
PASS
ok gott/hello 1.399s
32 B/op
:平均每次迭代内存分配的字节数2 allocs/op
:平均每次迭代内存分配的次数
平均每次迭代计算的依据应该使用的是 b.N=5137456
迭代次数
基准测试的用途
一般用于对比两个不同的操作所消耗的时间,如
渐近增长函数的运行时间
一个函数需要1ms处理1,000个元素,处理10000或1百万将需要多少时间呢
I/O缓存该设置为多大
基准测试可以帮助我们选择在性能达标情况下所需的最小内存
确定哪种算法更好
比较型的基准测试代码
func benchmark(b *testing.B, size int) { /* ... */ }
func Benchmark10(b *testing.B) { benchmark(b, 10) }
func Benchmark100(b *testing.B) { benchmark(b, 100) }
func Benchmark1000(b *testing.B) { benchmark(b, 1000) }
通过参数size
来控制输入的大小,而不是直接修改b.N
的值,除非你只是想知道一个固定大小的迭代的耗时,否则基准测试的结果将毫无意义