testing - 基准测试

    被认为是基准测试,通过 命令,加上 -bench 标志来执行。多个基准测试按照顺序运行。

    基准测试函数的形式如下:

    1. func BenchmarkHello(b *testing.B) {
    2. for i := 0; i < b.N; i++ {
    3. fmt.Sprintf("hello")
    4. }
    5. }

    基准函数会运行目标代码 b.N 次。在基准执行期间,程序会自动调整 b.N 直到基准测试函数持续足够长的时间。输出结果形如:

    1. BenchmarkHello 10000000 282 ns/op

    意味着循环执行了 10000000 次,每次循环花费 282 纳秒 (ns)。

    如果基准测试在循环前需要一些耗时的配置,则可以先重置定时器:

    1. func BenchmarkBigLen(b *testing.B) {
    2. big := NewBig()
    3. b.ResetTimer()
    4. for i := 0; i < b.N; i++ {
    5. big.Len()
    6. }
    7. }

    如果基准测试需要在并行设置中测试性能,则可以使用 RunParallel 辅助函数 ; 这样的基准测试一般与 go test -cpu 标志一起使用:

    接着上一节的例子,我们对 Fib 进行基准测试:

    1. func BenchmarkFib10(b *testing.B) {
    2. for n := 0; n < b.N; n++ {
    3. Fib(10)
    4. }
    5. }

    执行 go test -bench=.,输出:

    1. $ go test -bench=.
    2. BenchmarkFib10-4 3000000 424 ns/op
    3. PASS
    4. ok chapter09/testing 1.724s
    1. func BenchmarkFib1(b *testing.B) { benchmarkFib(1, b) }
    2. func BenchmarkFib3(b *testing.B) { benchmarkFib(3, b) }
    3. func BenchmarkFib10(b *testing.B) { benchmarkFib(10, b) }
    4. func BenchmarkFib20(b *testing.B) { benchmarkFib(20, b) }
    5. func BenchmarkFib40(b *testing.B) { benchmarkFib(40, b) }
    6. func benchmarkFib(i int, b *testing.B) {
    7. for n := 0; n < b.N; n++ {
    8. Fib(i)
    9. }

    再次执行 go test -bench=.,输出:

    默认情况下,每个基准测试最少运行 1 秒。如果基准测试函数返回时,还不到 1 秒钟,b.N 的值会按照序列 1,2,5,10,20,50,… 增加,同时再次运行基准测测试函数。

    我们注意到 BenchmarkFib40 一共才运行 2 次。为了更精确的结果,我们可以通过 -benchtime 标志指定运行时间,从而使它运行更多次。

    1. $ go test -bench=Fib40 -benchtime=20s
    2. BenchmarkFib40-4 30 838675800 ns/op

    B 类型

    B 是传递给基准测试函数的一种类型,它用于管理基准测试的计时行为,并指示应该迭代地运行测试多少次。

    当基准测试函数返回时,或者当基准测试函数调用 FailNowFatalFatalfSkipNowSkipSkipf 中的任意一个方法时,则宣告测试函数结束。至于其他报告方法,比如 LogError 的变种,则可以在其他 goroutine 中同时进行调用。

    跟单元测试一样,基准测试会在执行的过程中积累日志,并在测试完毕时将日志转储到标准错误。但跟单元测试不一样的是,为了避免基准测试的结果受到日志打印操作的影响,基准测试总是会把日志打印出来。

    B 类型中的报告方法使用方式和 T 类型是一样的,一般来说,基准测试中也不需要使用,毕竟主要是测性能。这里我们对 B 类型中其他的一些方法进行讲解。

    有三个方法用于计时:

    1. StartTimer:开始对测试进行计时。该方法会在基准测试开始时自动被调用,我们也可以在调用 StopTimer 之后恢复计时;
    2. StopTimer:停止对测试进行计时。当你需要执行一些复杂的初始化操作,并且你不想对这些操作进行测量时,就可以使用这个方法来暂时地停止计时;
    3. ResetTimer:对已经逝去的基准测试时间以及内存分配计数器进行清零。对于正在运行中的计时器,这个方法不会产生任何效果。本节开头有使用示例。

    body 函数将在每个 goroutine 中执行,这个函数需要设置所有 goroutine 本地的状态,并迭代直到 pb.Next 返回 false 值为止。因为 StartTimerStopTimeResetTimer 这三个方法都带有全局作用,所以 body 函数不应该调用这些方法; 除此之外,body 函数也不应该调用 Run 方法。

    具体的使用示例,在本节开头已经提供!

    ReportAllocs 方法用于打开当前基准测试的内存统计功能, 与 go test 使用 -benchmem 标志类似,但 ReportAllocs 只影响那些调用了该函数的基准测试。

    测试示例:

    1. func BenchmarkTmplExucte(b *testing.B) {
    2. b.ReportAllocs()
    3. templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    4. b.RunParallel(func(pb *testing.PB) {
    5. // Each goroutine has its own bytes.Buffer.
    6. var buf bytes.Buffer
    7. // The loop body is executed b.N times total across all goroutines.
    8. buf.Reset()
    9. templ.Execute(&buf, "World")
    10. }
    11. })
    12. }

    测试结果类似这样:

    1. BenchmarkTmplExucte-4 2000000 898 ns/op 368 B/op 9 allocs/op

    对上述结果中的每一项,你是否都清楚是什么意思呢?

    • 2000000 :基准测试的迭代总次数 b.N
    • 368 B/op:平均每次迭代内存所分配的字节数
    • 9 allocs/op:平均每次迭代的内存分配次数

    testing 包中的 BenchmarkResult 类型能为你提供帮助,它保存了基准测试的结果,定义如下:

    该类型还提供了每次迭代操作所消耗资源的计算方法,示例如下:

    1. package main
    2. import (
    3. "bytes"
    4. "fmt"
    5. "testing"
    6. "text/template"
    7. )
    8. func main() {
    9. benchmarkResult := testing.Benchmark(func(b *testing.B) {
    10. templ := template.Must(template.New("test").Parse("Hello, {{.}}!"))
    11. // RunParallel will create GOMAXPROCS goroutines
    12. // and distribute work among them.
    13. b.RunParallel(func(pb *testing.PB) {
    14. // Each goroutine has its own bytes.Buffer.
    15. var buf bytes.Buffer
    16. for pb.Next() {
    17. // The loop body is executed b.N times total across all goroutines.
    18. buf.Reset()
    19. templ.Execute(&buf, "World")
    20. }
    21. })
    22. })
    23. // fmt.Printf("%8d\t%10d ns/op\t%10d B/op\t%10d allocs/op\n", benchmarkResult.N, benchmarkResult.NsPerOp(), benchmarkResult.AllocedBytesPerOp(), benchmarkResult.AllocsPerOp())
    24. }

    导航

    • 下一节: