Go語言基準測試(benchmark)三部曲之一:基礎篇

2023-11-01 09:01:58

歡迎存取我的GitHub

這裡分類和彙總了欣宸的全部原創(含配套原始碼):https://github.com/zq2599/blog_demos

關於基準測試(benchmark)

  • Go的標準庫內建的testing框架提供了基準測試(benchmark)功能,可以用來驗證本地方法在序列或者並行執行時的基準表現,幫助開發者瞭解程式碼的真實效能情況,例如一個方法執行一次的平均耗時,還能看到記憶體分配的情況

關於Go語言基準測試(benchmark)三部曲

  • 《Go語言基準測試(benchmark)三部曲》是欣宸的優異又一些列原創,旨在通過簡單的編碼實戰與大家一同學習和鞏固基準測試的常見操作,共分為:基礎篇、記憶體篇、提高篇三部分,每篇都目標明確,用少量程式碼和命令快速熟悉對應知識點,相信《三部曲》結束後,您也能輕鬆完成基準測試,根高效的檢查程式碼效能

本篇概覽

  • 作為系列的開篇,本文的目標是和大家一起快速開始基準測試,然後將常用的引數和命令都用上一遍,具體步驟如下
  1. 編碼,寫個本地方法,後面就用benchmark來驗證這些方法的效能
  2. 最基本的基準測試
  3. 匹配規則
  4. -cpu引數
  5. -benchtime引數
  6. -count引數
  7. 並行測試

環境資訊

  • 作業系統:Windows 11 家庭中文版(22H2),12代i5處理器,16G記憶體
  • Go:1.19.3
  • VSCode:1.75.1

編碼

  • benchmark是用來對已有方法做測試的,因此一開始要把被測試的方法準備好,然後像單元測試那樣編寫benchmark測試程式碼,最後用go test做基準測試,咱們這就動手把方法準備好
  • 準備一個目錄,名為benchmark-demo,在目錄下執行以下命令,新建一個module
go mod init benchmark-demo
  • 用vscode開啟此目錄,會識別到module,接下來可以在vscode中操作了
  • 新建檔案benchmark-demo,裡面是用來做基準測試的方法,先寫一個簡單的方法fib
package main

// 斐波拉契數列
func fib(n int) int {
	if n == 0 || n == 1 {
		return n
	}

	return fib(n-2) + fib(n-1)
}
  • 接下來就用benchmark來測試fib方法,看看其效能情況

最基本的基準測試

  • 最基本的基準測試是從兩個維度去檢測方法的效能
  1. 指定時間內,檢查方法的執行的耗時
  2. 指定次數,檢查方法的執行的耗時
  • 具體操作分為兩部
  1. 寫benchmark測試方法,就像寫單元測試程式碼一樣,程式碼位於_test.go結尾的檔案中
  2. 執行benchmark測試
  • 先來寫benchmakr測試方法,新建名為main_test.go,裡面有個方法BenchmarkFib,注意要以Benchmark開始,入參是*testing.B型別,這就是最簡單的benchmark方法了
func BenchmarkFib(b *testing.B) {
	for n := 0; n < b.N; n++ {
		fib(30)
	}
}
  • 現在用命令列進行測試,執行下面這個最精簡的命令,注意:要執行benchmark測試就要帶-bench
go test -bench .
  • 很快就完成了基準測試,控制檯輸出如下,具體含義稍後解釋
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-16              322           3653937 ns/op
PASS
ok      benchmark-demo  1.999s
  • 最前面幾行是基本資訊, 整理如下
名稱 含義
goos 作業系統,這裡是windows
goarch CPU架構,這裡是64位元X86
pkg package名,可以在測試的時候指定package
cpu CPU的資訊,這裡可以看到是12代酷睿i5
  • 接下來是benchmark的結果,每個數位的具體解釋如下圖所示
  • benchmark也可以像普通單元測試那樣新增驗證邏輯的程式碼,測試結果可以是通過和不通過,BenchmarkFib中沒有像普通單元測試那樣的失敗邏輯,因此最終的判斷是測試通過,控制檯中輸出了PASS
  • 將同樣的測試在M1 Pro晶片的Mac Book Pro上執行一遍試試,獲取結果如下,可見和前面的windows測試結果大致相近,不同的是結果中沒有CPU資訊
go test -bench .
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8   	     326	   3647077 ns/op
PASS
ok  	benchmark-demo	1.654s
  • 以上就是最基礎的benchmark測試了,咱們已經驗證了,接下來試試那些常用的引數

匹配規則

  • 在有多個Benchmark測試方法的時候,如何做到只執行指定的方法呢?
  • 先看指定package的
  1. 指定package:go test -bench benchmark-demo
  2. 指定子package:go test -bench benchmark-demo/XXX
  3. 當前目錄下的所有package:go test -bench ./... (斜槓左側是一個點,右側是三個點)
  • 再看指定方法的,可以用正規表示式來指定方法名
  1. 所有以Fib結尾的方法:go test -bench='Fib$' benchmark-demo
  2. 所有以BenchmarkNew開始的方法:go test -bench='^BenchmarkNew' benchmark-demo
  • 接下來看幾個常用引數

-cpu引數

  • 前面的測試結果BenchmarkFib-16可以看出測試中的GOMAXPROCS等於16,這個值可以用-cpu引數來調整,不過咱們這裡不涉及並行程式設計,GOMAXPROCS的變化對測試結果沒有影響,改一下試試,果然沒啥波動(稍後還會講到並行測試,那時候cpu引數的作用就非常明顯了)
go test -bench='Fib$' -cpu=2 .  
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-2               320           3692123 ns/op
PASS
ok      benchmark-demo  1.962s

-benchtime引數

  • 前面的命令中我們並沒有指定測試時長,因此使用的是預設值1秒,現在咱們來修改這個引數試試,畢竟1秒內完成基準測試並不是普遍適用的
  • 指定基準測試時長為10秒:go test -bench='Fib$' -benchtime=10s benchmark-demo
  • 結果如下
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-16             3264           3668947 ns/op
PASS
ok      benchmark-demo  12.710s
  • 除了指定時間,還能指定次數,就是指定下圖黃色箭頭所指的值,也就是控制了被測試方法所執行的次數
  • 指定每輪基準測試內的迴圈次數為999次:go test -bench='Fib$' -benchtime=999x benchmark-demo
  • 注意將-benchtime的值從10s改為999x,測試範圍就從時間變成了次數,測試結果如下,可見準確的執行了999次
go test -bench='Fib$' -benchtime=999x benchmark-demo
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-16              999           3667776 ns/op
PASS
ok      benchmark-demo  4.006s

-count引數

  • count引數也是用來控制執行次數的,和前面提到的benchtime不同,count用來控制BenchmarkXXX方法的呼叫次數,而benchtime是用來控制BenchmarkXXX方法的入參b.N的值,如下圖所示
  • 指定每輪基準測試內的迴圈次數為999次,一共兩輪:go test -bench='Fib$' -benchtime=999x -count=2 benchmark-demo,輸出如下,還是很容易理解的
go test -bench='Fib$' -benchtime=999x -count=2 benchmark-demo
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-16              999           3656639 ns/op
BenchmarkFib-16              999           3645846 ns/op
PASS
ok      benchmark-demo  7.709s

並行測試

  • 前面的BenchmarkFib是常規的序列測試,如果被測試的方法在真實環境中存在並行呼叫,那麼在基準測試中也應該通過並行測試來了解其基本效能(例如鎖造成的阻塞)
  • 為了對fib方法做並行基準測試,需要編寫對應的基準測試程式碼,如下
func BenchmarkParallelFib(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			fib(30)
		}
	})
}
  • 執行go test -bench='^Benchmark' benchmark-demo,這時BenchmarkFibBenchmarkParallelFib都會執行,測試結果如下,可見相同時間內,執行fib的總次數是隨著並行數量而增加
go test -bench='^Benchmark' benchmark-demo       
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-16                      368           3362756 ns/op
BenchmarkParallelFib-16             3134            370352 ns/op
PASS
ok      benchmark-demo  3.189s
  • 通過cpu引數指定GOMAXPROCS數量,執行命令go test -bench='^Benchmark' -cpu=8 benchmark-demo,結果如下,可見序列方法的測試結果沒有變化,而並行測試的結果隨著GOMAXPROCS的減少有明顯下降
go test -bench='^Benchmark' -cpu=8 benchmark-demo
goos: windows
goarch: amd64
pkg: benchmark-demo
cpu: 12th Gen Intel(R) Core(TM) i5-1240P
BenchmarkFib-8                       356           3352500 ns/op
BenchmarkParallelFib-8              1989            582177 ns/op
PASS
ok      benchmark-demo  3.211s
  • 最後注意,除了cpu引數,前面用過的benchtime和count也都適用於並行測試,含義和序列測試的場景一致
  • 至此,最基本的基準測試已經完成了,下一篇咱們會進行記憶體相關的基準測試,觀察記憶體的使用和分配情況,敬請期待

歡迎關注部落格園:程式設計師欣宸

學習路上,你不孤單,欣宸原創一路相伴...