Golang基准测试benchmark—测试代码性能

前言

在优化代码或者决定算法选用的时候,性能是很重要的一个指标,比如我最近在做需求的时候需要用哈希算法做签名。一开始想都没想就想用md5,然后IDE上蹦出几个大字:md5有已知的安全问题,建议换成其他算法。然后就考虑换SHA256。问题会不会换一个算法导致性能急剧下降呢?

这时Go语言内置的Benchmark功能就很方便的派上用场了。注意,基准测试受环境影响大,应尽量保证环境稳定,测试时尽量CPU别同时干其他耗性能的事情,别开节能模式。

Go基准测试

Go基准测试是基于Go单元测试的。基准测试放在和单测一样的包下,依旧是xxxx_test.go这样的包下。

函数名约定

与单测函数不同的是,基准测试的函数以Benchmark来做开头,签名像这样:

func BenchmarkXxx(b *testing.B) { ... }

testing.B

N

基准测试的基本原理是让用户实现一个循环来调用被测算法,外层的函数传入要循环的次数,通过不断的尝试,使得 总耗时/循环次数 趋于稳定后就可以认为这个值就是在这个环境下被测算法的耗时,当然也可以通过指定循环次数等使得提前结束。

而外层函数就是通过 b.N把循环次数传递给我们的,所以在基准测试内应该要有个像这样的循环:

for n := 0; n < b.N; n++ {
	// 被测算法
}

ResetTimer()

在开始基准测试前,可能有一些很耗时的准备工作,这个时候就可以通过b.ResetTimer() 重新开始计时

StartTimer()和StopTimer()

如果是在每次循环前后都需要做耗时的准备工作,则可以使用b.StartTimer()b.StopTimer()的组合,来忽略那些准备阶段的时间。

其他方法

testing.b也提供正常的测试里提供的那些函数,如Fail使得测试失败,Run使得运行子测试,Helper来标记辅助函数。

指令

运行单测的 go test指令默认是会忽略基准测试的。
为了让go test能够运行基准测试,我们需要直接指定-bench标签,其参数是正则表达式。如果要运行当前包下所有的基准测试:

go test -bench=.

这样就会运行当前路径下所有的基准测试。

如果要运行所有名字包含go或lang的测试:

go test -bench="go|lang"

另外,由于默认会运行单测,为了不让单测的输出影响输出结果,可以故意指定个不存在的单测函数名的:

go test -bench="go|lang" -run=noExist

还有一些其他的相关指令:

option 描述
-benchmem 性能测试的时候显示测试函数的内存分配的统计信息,等价于在基准测试中调用b.ReportAllocs()
-count n 运行多少次,默认1次
-timeout t 超时时间,超过会panic,默认10分钟
-cpu 指定GOMAXPROCS,可以通过,传入一个列表
-benchtime 指定执行时间(e.g. 2s)或具体次数(e.g. 10x)

示例

继续我们的故事,我现在需要测测用md5和SHA256生成签名的效率差异,实际使用中是给一个uuid生成签名。于是基准测试就写成了这样:

util_test.go:

package util

import (
	"crypto/md5"
	"crypto/sha256"
	"github.com/google/uuid"
	"testing"
)

func BenchmarkSha256(b *testing.B) {
	target := []byte(uuid.New().String())
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		sha256.Sum256(target)
	}
}

func BenchmarkMd5(b *testing.B) {
	target := []byte(uuid.New().String())
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		md5.Sum(target)
	}
}

输出

运行测试:

admin@……:util$ go test -bench=. -run=none          
goos: darwin
goarch: amd64
pkg: ……/util
BenchmarkSha256-12       6331168               184 ns/op
BenchmarkMd5-12         11321952               103 ns/op
PASS
ok      ……/util    4.392s

看到报告里头函数后面的-12了吗?这个表示运行时对应的 GOMAXPROCS 的值。接着的 6331168 表示最后一次给的N值,也就是认为结果可信的那次的循环的次数,最后的 184 ns/op表示每次循环需要花费 184 纳秒。

我们可以看出,SHA256在uuid字符串的场景下,计算费时差不多是Md5的1.8倍,还算可以接受。

可以多用上几个选项来看看

admin@……:util$ go test -bench=. -run=none -count=3 -cpu=2,4 -benchmem
goos: darwin
goarch: amd64
pkg: ……/util
BenchmarkSha256-2        6375644               178 ns/op               0 B/op          0 allocs/op
BenchmarkSha256-2        6575397               180 ns/op               0 B/op          0 allocs/op
BenchmarkSha256-2        6646250               182 ns/op               0 B/op          0 allocs/op
BenchmarkSha256-4        6566167               183 ns/op               0 B/op          0 allocs/op
BenchmarkSha256-4        6476132               190 ns/op               0 B/op          0 allocs/op
BenchmarkSha256-4        6327001               192 ns/op               0 B/op          0 allocs/op
BenchmarkMd5-2          10067620               107 ns/op               0 B/op          0 allocs/op
BenchmarkMd5-2          11456790               104 ns/op               0 B/op          0 allocs/op
BenchmarkMd5-2          11314701               106 ns/op               0 B/op          0 allocs/op
BenchmarkMd5-4          10312569               105 ns/op               0 B/op          0 allocs/op
BenchmarkMd5-4          10565292               102 ns/op               0 B/op          0 allocs/op
BenchmarkMd5-4          11695822               103 ns/op               0 B/op          0 allocs/op
PASS
ok      ……/util    17.036s

上面的测试中,我们指定了要运行3趟测试,分别用2核和4核来测试,并输出了内存的数据。可以看出,几次测试有波动,核数增加对运行速度并没什么软用,应为这两个算法都是串行调用。而且这两算法都不需要分配内存。

结语

当你没法确定你的优化到底有没效果时,不懂到时候绩效咋写时,不妨试试用基准测试测下性能吧!

你可能感兴趣的:(Golang,测试,golang,单元测试,基准测试,软件测试)