一個 Benchmark 比較分析工具 benchstat

在 Go 中,通過撰寫 Benchmark 函數可以很方便地對某個功能點進行性能檢測。對於重要的函數,我們可以在 CI/CD 中添加相應的測試流程,當函數性能發生變化時能夠及時感知。那問題來了,如何檢測函數的性能變化?

換個說法,你編寫了某功能函數但發現它運行很慢,需要對該函數進行優化,當你在谷歌搜索找到更好的實現方式,通過 Benchmark 函數發現它的確變快了。但你說不清楚具體變快了多少,你想知道函數優化前後的性能對比,提高多少百分點,可信度高嗎?

針對以上的需求場景,有一個工具可以幫助到你,它就是 benchstat。

Benchmark 示例

我們先回顧一下基準測試。爲了方便理解,這裏以計算經典的計算斐波那契數列值爲例。

func FibSolution(n int) int {
 if n < 2 {
  return n
 }

 return FibSolution(n-1) + FibSolution(n-2)
}

上述代碼是遞歸式實現,很明顯,當 n 越來越大時,該函數的運行會變得非常耗時。以 n 爲 20 爲例,Benchmark 函數如下

func BenchmarkFib20(b *testing.B) {
 for i := 0; i < b.N; i++ {
  FibSolution(20)
 }
}

命令行執行go test -bench=BenchmarkFib20得到性能結果

BenchmarkFib20-8           39452             30229 ns/op

其中,-8 代表的是 8 cpu,函數運行次數爲 39452,每次函數的平均花費時間爲 30229ns。如果我們想得到多次樣本數據,可以指定 go test 的 -count=N 參數。例如想得到 5 次樣本數據,則執行go test -bench=BenchmarkFib20 -count=5

BenchmarkFib20-8           39325             30297 ns/op
BenchmarkFib20-8           39216             30349 ns/op
BenchmarkFib20-8           39901             30251 ns/op
BenchmarkFib20-8           39336             30455 ns/op
BenchmarkFib20-8           39423             30894 ns/op

計算斐波那契數列值的迭代式實現如下

func FibSolution(n int) int {
 if n < 2 {
  return n
 }
 p, q, r := 0, 0, 1
 for i := 2; i <= n; i++ {
  p = q
  q = r
  r = p + q
 }
 return r
}

對比這兩種函數的性能差異,最樸素的方式就是分別對這兩個函數進行基準測試,然後通過手工分析這些基準測試結果,但是這並不直觀。

benchstat

benchstat 是 Go 官方推薦的一款命令行工具,它用於計算和比較基準測試的相關統計數據。

我們可以通過以下命令進行安裝

go install golang.org/x/perf/cmd/benchstat@latest

執行 -h 參數可以看到該工具的使用描述

~ $ benchstat -h
usage: benchstat [options] old.txt [new.txt] [more.txt ...]
options:
  -alpha α
     consider change significant if p < α (default 0.05)
  -csv
     print results in CSV form
  -delta-test test
     significance test to apply to delta: utest, ttest, or none (default "utest")
  -geomean
     print the geometric mean of each file
  -html
     print results as an HTML table
  -norange
     suppress range columns (CSV only)
  -sort order
     sort by order: [-]delta, [-]name, none (default "none")
  -split labels
     split benchmarks by labels (default "pkg,goos,goarch")

我們想比較 FibSolution(n) 從 15 到 20,兩種實現方式的性能基準測試。

$ go test -bench=. -count=| tee old.txt
$ go test -bench=. -count=| tee new.txt

注意,這兩條命令執行時,分別對應 FibSolution 函數採用遞歸式和迭代式實現邏輯。

此時,我們可以對這兩個函數實現邏輯進行性能對比

 $ benchstat old.txt new.txt 
name     old time/op  new time/op  delta
Fib15-8  2.67µs ± 2%  0.01µs ± 5%  -99.81%  (p=0.008 n=5+5)
Fib16-8  4.20µs ± 1%  0.01µs ± 2%  -99.87%  (p=0.008 n=5+5)
Fib17-8  6.81µs ± 0%  0.01µs ± 2%  -99.92%  (p=0.008 n=5+5)
Fib18-8  11.1µs ± 1%   0.0µs ± 1%  -99.95%  (p=0.008 n=5+5)
Fib19-8  18.0µs ± 2%   0.0µs ± 4%  -99.97%  (p=0.008 n=5+5)
Fib20-8  29.2µs ± 1%   0.0µs ± 3%  -99.98%  (p=0.008 n=5+5)

可以看到,遞歸式實現的函數,他的執行時間隨着 n 值變大增加非常明顯。迭代式實現方式,相較於遞歸式,它的平均時間開銷降低了 99 % 以上,優化效果非常明顯。

另外,p=0.008 表示結果的可信程度,p 值越大表明可信度越低。一般以 0.05 作爲臨界值,超過該值,則結果不可信。n=5+5 表示分別使用的有效樣本數量。

總結

benchstat 是一個基準測試統計工具,當我們做一些優化工作時,可以利用它減輕人工分析數據成本。

如果你的項目在 CI/CD 流程中有部署自動化測試,那不妨將該工具加入進來。在對函數有改動且加劇了性能損耗時,它或許能幫助你提前發現問題。

Go 語言進階學習 該公衆號專注於分享 Go 語言相關的技術文章、工具資源、學習視頻、學習資料、送書活動等,您的關注是我們的奮鬥源動力~~~ 關注我們,您收穫的不只是知識,還有經驗與人脈!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Q4Sl6yWOHM90lV5iF8EuRw