Go语言精进之路:从新手到高手的编程思想、方法和技巧(2)
上QQ阅读APP看书,第一时间看更新

46.1 性能基准测试在Go语言中是“一等公民”

在前文中,我们已经接触过许多性能基准测试。和上一条所讲的模糊测试的境遇不同,性能基准测试在Go语言中是和普通的单元测试一样被原生支持的,得到的是“一等公民”的待遇。我们可以像对普通单元测试那样在*_test.go文件中创建被测对象的性能基准测试,每个以Benchmark前缀开头的函数都会被当作一个独立的性能基准测试:

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

下面是一个对多种字符串连接方法的性能基准测试(改编自第15条):

// chapter8/sources/benchmark_intro_test.go

var sl = []string{
    "Rob Pike ",
    "Robert Griesemer ",
    "Ken Thompson ",
}

func concatStringByOperator(sl []string) string {
    var s string
    for _, v := range sl {
        s += v
    }
    return s
}

func concatStringBySprintf(sl []string) string {
    var s string
    for _, v := range sl {
        s = fmt.Sprintf("%s%s", s, v)
    }
    return s
}

func concatStringByJoin(sl []string) string {
    return strings.Join(sl, "")
}

func BenchmarkConcatStringByOperator(b *testing.B) {
    for n := 0; n < b.N; n++ {
        concatStringByOperator(sl)
    }
}

func BenchmarkConcatStringBySprintf(b *testing.B) {
    for n := 0; n < b.N; n++ {
        concatStringBySprintf(sl)
    }
}

func BenchmarkConcatStringByJoin(b *testing.B) {
    for n := 0; n < b.N; n++ {
        concatStringByJoin(sl)
    }
}

上面的源文件中定义了三个性能基准测试:BenchmarkConcatStringByOperator、Benchmark-ConcatStringBySprintf和BenchmarkConcatStringByJoin。我们可以一起运行这三个基准测试:

$go test -bench . benchmark_intro_test.go
goos: darwin
goarch: amd64
BenchmarkConcatStringByOperator-8       12810092            88.5 ns/op
BenchmarkConcatStringBySprintf-8         2777902             432 ns/op
BenchmarkConcatStringByJoin-8           23994218            49.7 ns/op
PASS
ok         command-line-arguments 4.117s

也可以通过正则匹配选择其中一个或几个运行:

$go test -bench=ByJoin ./benchmark_intro_test.go
goos: darwin
goarch: amd64
BenchmarkConcatStringByJoin-8     23429586            49.1 ns/op
PASS
ok         command-line-arguments 1.209s

我们关注的是go test输出结果中第三列的那个值。以BenchmarkConcatStringByJoin为例,其第三列的值为49.1 ns/op,该值表示BenchmarkConcatStringByJoin这个基准测试中for循环的每次循环平均执行时间为49.1 ns(op代表每次循环操作)。这里for循环调用的是concatStringByJoin,即执行一次concatStringByJoin的平均时长为49.1 ns。

性能基准测试还可以通过传入-benchmem命令行参数输出内存分配信息(与基准测试代码中显式调用b.ReportAllocs的效果是等价的):

$go test -bench=Join ./benchmark_intro_test.go -benchmem
goos: darwin
goarch: amd64
BenchmarkConcatStringByJoin-8     23004709   48.8 ns/op   48 B/op     1 allocs/op
PASS
ok         command-line-arguments 1.183s

这里输出的内存分配信息告诉我们,每执行一次concatStringByJoin平均进行一次内存分配,每次平均分配48字节的数据。