Featured image of post Go高性能编程 EP5: 更精准的benchmark

Go高性能编程 EP5: 更精准的benchmark

 

当我们尝试去优化代码的性能时,首先得知道当前的性能怎么样,得到一个基准性能。Go语言标准库内置的 testing 测试框架提供了benchmark的能力。本文主要介绍 如何使用benchmark 进行基准测试,以及如何提高benchmark 的精准度,最后介绍了两个工具,帮助我们更加方便的进行benchmark。

This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on Medium. Thank you very much.

稳定的测试环境

性能测试受环境的影响很大,为了保证测试的可重复性,在进行性能测试时,尽可能地保持测试环境的稳定。

  • 机器处于闲置状态,测试时不要执行其他任务,也不要和其他人共享硬件资源。
  • 机器是否关闭了节能模式,一般笔记本会默认打开这个模式,测试时关闭。
  • 避免使用虚拟机和云主机进行测试,一般情况下,为了尽可能地提高资源的利用率,虚拟机和云主机 CPU 和内存一般会超售,超售机器的性能表现会非常地不稳定。
  • 一次测试是没有意义的,统计意义下可对比的结果是关键,对于一般的benchmark,我一般会重复执行20次。

benchmark 是如何工作的

benchmark 其实就是重复调用某个函数,然后记录函数的执行时间等指标,来度量它的性能。一个典型的benchamrk 函数如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func fib(n int) int {  
    if n < 2 {  
       return n  
    }  
    return fib(n-1) + fib(n-2)  
}  
func BenchmarkFib20(b *testing.B) {  
    for i := 0; i < b.N; i++ {  
       // Call the function we're benchmarking  
       fib(20)  
    }  
}

benchmark 函数必须以 Benchmark 开始 我们可以这样运行它,都是等效的。

1
2
go test -bench .
go test -bench="BenchmarkFib20"

b.N

benchmark 用例的参数 b *testing.B,有个属性 b.N 表示这个用例需要运行的次数。b.N 对于每个用例都是不一样的。
那这个值是如何决定的呢?b.N 从 1 开始,如果该用例能够在 1s 内完成,b.N 的值便会增加,再次执行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快

进阶参数

  • count 控制运行次数,而不受由b.N 控制
  • benchtime 控制运行时间,而受1s控制
  • benchmem 度量内存分配的次数
  • cpu 指定使用几个CPU核心,默认使用的是go
    同时,benchmark 测试支持go test 的其他参数,比如cpuprofilememprofiletrace

提升准确度

降低系统噪音:perflock

perflock 作用是限制 CPU 时钟频率,从而一定程度上消除系统对性能测试程序的影响,减少结果的噪声,进而性能测量的结果方差更小也更加可靠。

ResetTimer

如果在 benchmark 开始前,需要一些准备工作,如果准备工作比较耗时,则需要将这部分代码的耗时忽略掉。

StopTimer & StartTimer

StopTimer & StartTimer 也是相同原理,每次函数调用前后需要一些准备工作和清理工作,我们可以使用 StopTimer 暂停计时以及使用 StartTimer 开始计时。

度量 benchmark

benchstat 是官方提供的一个对比工具,用于比较两次benchmark之间的性能差别

Benchstat computes statistical summaries and A/B comparisons of Go benchmarks.

我们用benchstat对比一下 bubbleSort 跟quickSort 的性能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func softNums() {  
    //bubbleSort(nums)  
    //quickSort(nums)
}  
  
func initNums(count int) {  
    rand.Seed(time.Now().UnixNano())  
    nums = make([]int, count)  
    for i := 0; i < count; i++ {  
       nums[i] = rand.Intn(count)  
    }  
}  
func BenchmarkSoftNums(b *testing.B) {  
    initNums(10000)  
    b.ResetTimer()  
    for i := 0; i < b.N; i++ {  
       softNums()  
    }  
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
go test -bench="BenchmarkSoftNums" -count=10 |tee quicksoft.txt
go test -bench="BenchmarkSoftNums" -count=10 |tee bubblesoft.txt

➜  benchmark git:(main) ✗ benchstat bubblesoft.txt quicksoft.txt                         
goos: darwin
goarch: arm64
pkg: blog-example/go/benchmark
            │ bubblesoft.txt │            quicksoft.txt            │
            │     sec/op     │   sec/op     vs base                │
SoftNums-10    31942.4µ ± 1%   775.8µ ± 2%  -97.57% (p=0.000 n=10)

这样的结果是不是很清晰?我们可以把他放在报告或者github issue 中,很专业。

一些第三方的工具

bench

bench 是一个为 Go 程序提供集成性能测量、自动性能锁定、统计分析和颜色指示的基准测试工具。

funcbench

funcbench 是 Prometheus 项目中用于自动化 Go 代码基准测试和性能比较的工具。以下是其主要特点和功能:
支持比较本地分支与GitHub 分支、性能锁定、性能比较 等特点,有利于提高开发效率。如果项目比较大,可以参考这种方式。

参考资料

true
最后更新于 Jul 12, 2024 15:05 CST
使用 Hugo 构建
主题 StackJimmy 设计