歡迎訪問我的GitHub 這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos 本篇概覽 -《Go語言基準測試(benchmark)三部曲》已近尾聲,經歷了《基礎篇》和《記憶體篇》的實戰演練,相信您已熟練掌握了基準測試的常規操作以及各種 ...
歡迎訪問我的GitHub
這裡分類和彙總了欣宸的全部原創(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
-《Go語言基準測試(benchmark)三部曲》已近尾聲,經歷了《基礎篇》和《記憶體篇》的實戰演練,相信您已熟練掌握了基準測試的常規操作以及各種參數的用法,現在可以學習一些進階版的技能了,在面對複雜一些的場景也能高效完成基準測試,另外還有幾個坑也要提前瞭解,避免以後掉進去
ResetTimer
- 有時候,在基準測試前會有些準備工作,這些準備工作的耗時會影響基準測試的結果,舉例如下,BenchmarkFib是常規的基準測試,而BenchmarkFibWithPrepare多了八百毫秒的準備時間
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(30)
}
}
// BenchmarkFibWithPrepare 進入正式測試前需要耗時做準備工作的case
func BenchmarkFibWithPrepare(b *testing.B) {
// 假設這裡有個耗時800毫秒的初始化操作
<-time.After(800 * time.Millisecond)
// 這下麵才是咱們真正想做基準測試的代碼
for n := 0; n < b.N; n++ {
fib(30)
}
}
- 同時執行上述兩個基準測試,命令和結果如下,可見因為準備工作的耗時,BenchmarkFibWithPrepare方法的測試結果遠不及BenchmarkFib,這與事實是不符合的,因為BenchmarkFibWithPrepare方法的測試目標沒有變化,但是因為自身的準備工作導致測試結果出現較大偏差
go test -bench='BenchmarkFib|BenchmarkFibWithPrepare' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 325 3637442 ns/op
BenchmarkFibWithPrepare-8 50 20173566 ns/op
PASS
ok benchmark-demo 14.871s
- 解決上述問題的思路是不要將準備工作的耗時算入基準測試,實現起來很簡簡,如下圖黃色箭頭所示,b.ResetTimer()重置了計時器,前面的耗時都與基準測試無關
- 再做一次基準測試,結果如下,可見800毫秒帶來的偏差已被去除
go test -bench='BenchmarkFib|BenchmarkFibWithPrepare' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 325 3616239 ns/op
BenchmarkFibWithPrepare-8 316 3729323 ns/op
PASS
ok benchmark-demo 5.628s
StopTimer & StartTimer
- 前面通過ResetTimer消除了基準測試前的多餘耗時,但是如果多餘的耗時出現在基準測試過程中呢?代碼如下所示,fib是本次測試的目標,如果每次fib結束後都要做一些耗時的清理工作(這裡用10毫秒延時來模仿),才能再次fib,那又該如何消除這10毫秒對基準測試的影響呢?
func BenchmarkFibWithClean(b *testing.B) {
// 這下麵才是咱們真正想做基準測試的代碼
for n := 0; n < b.N; n++ {
fib(30)
// 假設這裡有個耗時100毫秒的清理操作
<-time.After(10 * time.Millisecond)
}
}
- 先來看看每次fib之後的10毫秒是否會影響基準測試,執行測試的命令和測試結果如下,可見,和沒有任何耗時的BenchmarkFib方法相比,BenchmarkFibWithClean的測試結果與fib的真實性能相去甚遠
go test -bench='BenchmarkFib$|BenchmarkFibWithClean' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 322 3610100 ns/op
BenchmarkFibWithClean-8 81 16139196 ns/op
PASS
ok benchmark-demo 3.002s
- 對於這種每次調用fib之前或者之後都會出現的額外耗時操作,可以用b.StartTimer()和b.StopTimer()的組合來消除掉,簡單的說就是StartTimer會開啟基準測試的計時,StopTimer會暫停計時,具體的使用方法如下
// BenchmarkFibWithClean 假設每次執行完fib方法後,都要做一次清理操作
func BenchmarkFibWithClean(b *testing.B) {
// 這下麵才是咱們真正想做基準測試的代碼
for n := 0; n < b.N; n++ {
// 繼續記錄耗時
b.StartTimer()
fib(30)
// 停止記錄耗時
b.StopTimer()
// 假設這裡有個耗時100毫秒的清理操作
<-time.After(10 * time.Millisecond)
}
}
- 再次測試,結果如下,去除了多餘耗時的基準測試結果,從之前16139196ns恢復到7448678ns,然而,和原始的沒有任何處理的BenchmarkFib結果相比依然有一倍左右的差距,看來StartTimer和StopTimer本身也會帶來耗時,而且在納秒級別的測試中會顯得非常明顯
go test -bench='BenchmarkFib$|BenchmarkFibWithClean' benchmark-demo
goos: darwin
goarch: arm64
pkg: benchmark-demo
BenchmarkFib-8 325 3631020 ns/op
BenchmarkFibWithClean-8 241 7448678 ns/op
PASS
ok benchmark-demo 7.751s
危險用法,提前避開
- 現在咱們對benchmark的瞭解已經比較全面了,可以覆蓋大多數單元測試場景,下麵有兩個反面教材,希望咱們將來都能提前避免類似錯誤
- 這兩個反面教材比較類似:對b.N的錯誤使用
- 第一個錯誤用法如下所示,在執行b.N次迴圈的時候,將當前是第幾次作為入參傳入了被測試的方法fib
// BenchmarkFibWrongA 演示了錯誤的基準測試代碼,這樣的測試可能無法結束
func BenchmarkFibWrongA(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(n)
}
}
- 上述代碼在基準測試的時候可能永遠不會結束,這是因為b.N的值並不固定,可能超出了fib方法的設計範圍,這樣就導致出現意料之外的結果(本意是性能測試,fib的入參應該是設計範圍內的),實際運行效果如下,紅色箭頭指向的狀態一直在等待中,只能強行關閉了
- 第二種反面教材也類似,不過更簡單,直接拿b.N作為入參,只調用一次fib方法,代碼如下所示
func BenchmarkFibWrongB(b *testing.B) {
fib(b.N)
}
- 和前面的BenchmarkFibWrongA比,fib的執行次數似乎少了,但是請註意:b.N到底是多少呢?是否在fib方法的設計範圍內?依舊沒有明確答案,因此,代碼也有可能永遠不會結束
- 以本例中的fib為例,實際功能是斐波那契數列,我這邊入參等於50的時候,fib方法的耗時是54秒,所以,如果b.N的值再大一些,例如等於100的時候,fib方法就要計算很久了,而計算較大值並不是我們做基準測試的意圖
- 至此,Go語言基準測試(benchmark)三部曲就全部完成了,相信此刻的您對除了信心滿滿,還有就是迫不及待的想去寫上一段benchmark代碼,看看自己的方法函數究竟性能如何吧
- 希望這三篇文章能給您帶來一些參考,golang學習路上,欣宸一路相伴