golang中,可以使用自带的benchmark来进行性能测试。
写法与单元测试很类似:文件名仍以xxx_test.go来命名,函数以Benchmark为前缀名,并且带有一个testing.B类型的参数。实例:
func BenchmarkXXX(b *testing.B) {
// 与性能测试无关的代码
b.ResetTimer()
for i:=0;i<b.N;i++ {
//测试代码
}
b.StopTimer()
// 与性能测试无关的代码
}
具体的测试代码就放在循环里。
testing.B 拥有testing.T 的全部方法,还有其它一些性能度量函数,如:
- SetBytes( i uint64) 统计内存消耗
- SetParallelism(p int) 制定并行数目;
- Run(name string, f func(b *testing.B))
- RunParallel(body func(*testing.PB)) 并行执行
- ReportAllocs(相当于加-benchmem)
对应不同的写法,这只是一种简单普遍的写法。
使用BenchMark测试性能
这里我们来测试两种字符串拼接方法的性能,第一种是使用“+”号,第二种是使用strings.builder
代码:
package benchmarktest
go test -bench=. -benchmem
*/
import (
"strings"
"testing"
)
func BenchmarkConcatStringByAdd(b *testing.B) {
elems := []string{"1", "2", "3", "4", "5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
res := ""
for _, v := range elems {
res += v
}
}
b.StopTimer()
}
func BenchmarkConcatStringByStringsBuilder(b *testing.B) {
elems := []string{"1", "2", "3", "4", "5"}
b.ResetTimer()
for i := 0; i < b.N; i++ {
var buf strings.Builder
for _, v := range elems {
buf.WriteString(v)
}
}
b.StopTimer()
}
在该目录下执行命令go test -bench=.
这里的“.”表示执行所有的benchmark,也可以加上函数名指定具体要执行的benchmark函数
如果在其它目录下执行,后面还要加上文件相对于$GOPATH/src的目录,如
go test -bench=. aboutGo/benchmarkTest
执行结果:
goos: darwin
goarch: amd64
pkg: aboutGo/benchmarkTest
BenchmarkConcatStringByAdd-4 8679672 130 ns/op
BenchmarkConcatStringByStringsBuilder-4 25238500 43.3 ns/op
PASS
ok aboutGo/benchmarkTest 2.417s
第一列数字表示测试次数,一个测试用例的默认测试时间是 1 秒,所以表示1s内的执行次数,这个时间可以指定,使用**-benchtime=5s**;
ns/op表示每一次操作耗费多少时间(纳秒);
因此从测试结果来看后者的耗时远小于前者。
我们知道,前者性能低下的原因可能与内存使用有关,因为golang中的string是字节切片,用“+”往里面拼接需要新建一个字节切片然后再往里面复制,可以使用benchmark来观察,使用命令:go test -bench=. -benchmem
执行结果:
goos: darwin
goarch: amd64
pkg: aboutGo/benchmarkTest
BenchmarkConcatStringByAdd-4 8786038 130 ns/op 16 B/op 4 allocs/op
BenchmarkConcatStringByStringsBuilder-4 24294679 44.6 ns/op 8 B/op 1 allocs/op
PASS
ok aboutGo/benchmarkTest 2.422s
前两个参数含义与之前一致,B/op表示每次操作需要分配多少个字节,allocs/op表示每次操作有多少次alloc(内存分配)。