go单元测试之benchmark基准测试详解

目录

与普通测试的区别

举例说明

指令与结果解读

性能比较

并行测试


 

与普通测试的区别

函数参数类型为*testing.B

测试函数名称必须以Benchmark 开头

执行基准测试时,需要添加-bench参数

运行所有基准测试函数

go test –bench=.*

举例说明

编写一个对于for循环的基准测试

func NewStringSlice(n int) []string {
    rand.Seed(time.Now().UnixNano())
    arr := make([]string, 0, n)
    for i := 0; i < n; i++ {
        arr = append(arr, strconv.Itoa(rand.Int()))
    }

    return arr
}


func BenchmarkStringSlice100(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NewStringSlice(100)
    }
}

func BenchmarkStringSlice1000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NewStringSlice(1000)
    }
}

func BenchmarkStringSlice8000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NewStringSlice(8000)
    }
}

func BenchmarkStringSlice9000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NewStringSlice(9000)
    }
}

func BenchmarkStringSlice10000(b *testing.B) {
    for i := 0; i < b.N; i++ {
        NewStringSlice(10000)
    }
}

运行所有BenchmarkStringSlice开头的基准测试函数:

go test -bench=^BenchmarkStringSlice -benchtime=5s .\testt\ -benchmem

结果如下:

BenchmarkStringSlice100-16                512133             12192 ns/op            4191 B/op        101 allocs/op
BenchmarkStringSlice1000-16               116264             49866 ns/op           40375 B/op       1001 allocs/op
BenchmarkStringSlice8000-16                17469            341199 ns/op          323003 B/op       8001 allocs/op
BenchmarkStringSlice9000-16                15597            383283 ns/op          363379 B/op       9001 allocs/op
BenchmarkStringSlice10000-16               14016            425329 ns/op          403754 B/op      10001 allocs/op
PASS
ok      awesomeProject/testt    43.135s

指令与结果解读

BenchmarkStringSlice100-16结尾的数字16表示GOMAXPROCS的值

-bench=^BenchmarkStringSlice表示所执行的基准测试范围包括以IntSlice结尾的基准测试函数,如果是-bench=abc$则表示所有以abc结尾的基准测试函数

-benchtime=5s表示性能测试运行的时间,默认是1s(1秒是最小值),有时候为了产生更准确的结果,可以增加-benchtime参数,用来指定最小基准时间。

后面的.\testt\表示执行的是testt目录下的测试文件。

-benchmem参数可用来获取执行性能测试时的内存分配数据

【结果】

512133表示发生的调用次数
12192 ns/op表示512133次调用消耗的平均时间为12192纳秒
4191 B/op表示每次操作内存分配了4191字节
101 allocs/op表示每次操作进行的内存分配次数,为101次。

 

性能比较

当算法不同时,我们仅需要以不同的输入来比较不同算法的性能差异,举个例子平时常见的比较有两种:

1、同一个函数,将不同的入参输入,比较不同参数的效果
2、不同的函数,将相同的入参输入,比较各自的效果

显然,做性能比较就是后者,而前者是对一个函数在不同条件下的性能测试。

作者编写了如下三种斐波那契数列的算法实现:

Fibonacci1

func Fibonacci1(num int) int {
    if num < 1 {
        return 0
    }

    if num == 1 || num == 2 {
        return 1
    }
    return Fibonacci1(num-1) + Fibonacci1(num-2)
}

Fibonacci2

func Fibonacci2(num int) int {
    if num < 1 {
        return 0
    }

    backMap := make(map[int]int)
    return fib(backMap, num)
}

func fib(backMap map[int]int, num int) int {
    if num == 1 || num == 2 {
        return 1
    }

    if backMap[num] != 0 {
        return backMap[num]
    }

    backMap[num] = fib(backMap, num-1) + fib(backMap, num-2)
    return backMap[num]
}

Fibonacci3

func Fibonacci3(num int) int {
    if num < 1 {
        return 0
    }
    
    if num == 1 || num == 2 {
        return 1
    }
    
    left, right, res := 1, 1, 0

    for i := 3; i <= num; i++ {
        res = left + right
        left, right = right, res
    }

    return res
}

如上都需要一个数值参数,获得一个数值,我们要做的就是计算在相同的入参情况下,三种算法的各自表现。

编写三种算法的基准测试基础函数

func benchmarkFib1(b *testing.B, num int) {
    for i := 0; i < b.N; i++ {
        Fibonacci1(num)
    }
}

func benchmarkFib2(b *testing.B, num int) {
    for i := 0; i < b.N; i++ {
        Fibonacci2(num)
    }
}

func benchmarkFib3(b *testing.B, num int) {
    for i := 0; i < b.N; i++ {
        Fibonacci3(num)
    }
}

另外编写外层调用,以入参为10举例:

func BenchmarkFib10_1(b *testing.B) { benchmarkFib1(b, 10) }
func BenchmarkFib10_2(b *testing.B) { benchmarkFib2(b, 10) }
func BenchmarkFib10_3(b *testing.B) { benchmarkFib3(b, 10) }

想比较什么值,传不同的值即可。

第一波,验证下入参都为10时的各自效果:

go test -bench=^BenchmarkFib10 -benchtime=10s .\testt\ -benchmem

BenchmarkFib10_1-16     94737864               129.1 ns/op             0 B/op          0 allocs/op
BenchmarkFib10_2-16     75052676               163.5 ns/op             0 B/op          0 allocs/op
BenchmarkFib10_3-16     1000000000               1.272 ns/op           0 B/op          0 allocs/op
PASS
ok      awesomeProject/testt    26.942s

就结果而言,算法3明显更优,能支持的执行次数更大,每次调用时间更短,百倍数量级差异。

接着,验证下入参都为50时的各自效果。由于传入数值越大消耗时间就越长,因此为得到更准确的结果,我们将-benchtime调大:

go test -bench=^BenchmarkFib50 -benchtime=50s .\testt\ -benchmem

BenchmarkFib50_1-16            1        70255751500 ns/op           2360 B/op          8 allocs/op
BenchmarkFib50_2-16     18577252              9417 ns/op            2401 B/op          9 allocs/op
BenchmarkFib50_3-16     706275607               81.14 ns/op            0 B/op          0 allocs/op
PASS
ok      awesomeProject/testt    369.285s

当入参为50,在相同的时间内(50秒),算法1只被调用了1次,而算法2则是其千万级的数量级优化,算法3更甚。

如果你对第二列被调用了多少次不敏感,或者没什么概念的话,可以这么理解:

执行入参为50的斐波那契数列,在给定的相同时间内,算法1只能执行1次,换句话说它没有机会再执行第二次了;

算法2在相同条件下却可以执行18577252次,消耗160秒;算法3干相同的活在相同条件下可以干706275607次,一共只花了70秒。

并行测试

即以并行的方式执行给定的基准测试。

b.RunParallel 会创建出多个goroutine,并将b.N分配给这些goroutine执行,其中goroutine数量的默认值为GOMAXPROCS。

如果想要增加非CPU受限(non-CPU-bound)基准测试的并行性, 那么可以在RunParallel之前调用 SetParallelism 。举例如下:

func BenchmarkFibParallel10_1(b *testing.B) {
    b.SetParallelism(2)
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            Fibonacci1(10)
        }
    })
}

执行:

go test -bench=^BenchmarkFibParallel10 -benchtime=10s .\testt\ -benchmem -cpu=1

BenchmarkFibParallel10_1        74194387               310.1 ns/op             0 B/op          0 allocs/op
BenchmarkFibParallel10_2        71352631               165.9 ns/op             0 B/op          0 allocs/op
BenchmarkFibParallel10_3        1000000000               1.203 ns/op           0 B/op          0 allocs/op
PASS
ok      awesomeProject/testt    37.271s

 

 

你可能感兴趣的:(快速查阅,go,golang,后端)