go benchmark 基準(zhǔn)測試詳解
一、benchmark的使用
1.一個(gè)簡單的例子
go mod init test 創(chuàng)建項(xiàng)目test,創(chuàng)建目錄bench/fib
創(chuàng)建fib.go
package fib
func fib(n int) int {
if n == 0 || n == 1 {
return n
}
return fib(n-1) + fib(n-2)
}創(chuàng)建fib_test.go
package fib
import (
"testing"
)
func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
fib(30)
}
}go 不管是單元測試還是基準(zhǔn)測試,測試函數(shù)都應(yīng)該寫在以_test.go 為結(jié)尾的文件中。
go 單元測試函數(shù)以Test開頭,函數(shù)參數(shù)為*testing.T
go 基準(zhǔn)測試函數(shù)以Bench開頭,函數(shù)參數(shù)為*testing.B
2.運(yùn)行用例
如何運(yùn)行測試用例呢?
運(yùn)行單元測試:go test -run=xxx,其中xxx為正則表達(dá)式,用來匹配單元測試函數(shù)的函數(shù)名。
運(yùn)行基準(zhǔn)測試:go test -bench=xxx,其中xxx為正則表達(dá)式,用來匹配基準(zhǔn)測試函數(shù)的函數(shù)名。
上述命令只能運(yùn)行當(dāng)前目錄中的測試用例,如果想運(yùn)行其他目錄的測試用例呢?
go test -bench=. test/bench/fib ,指定目標(biāo)包在項(xiàng)目中的絕對路徑。
go test -bench=. ./fib , 運(yùn)行當(dāng)前目錄下的子目錄fib中的測試。
go test -bench=. ./... , 運(yùn)行當(dāng)前目錄下的所有的package中的測試。

對-bench=加上正則表達(dá)式:
go test -bench=^BenchmarkFib ./fib

3.benchmark 是如何工作的
benchmark用例的參數(shù)為b testing.B, b.N 表示要測試的內(nèi)容運(yùn)行的次數(shù),這個(gè)次數(shù)對于每個(gè)用例都不同。那么這個(gè)次數(shù)變化規(guī)律是什么呢?b.N從1開始,如果內(nèi)容在1s內(nèi)運(yùn)行結(jié)束,那么b.N會(huì)增加,測試會(huì)再次執(zhí)行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 這樣的序列遞增,越到后面,增加得越快。
修改測試的內(nèi)容,讓運(yùn)行時(shí)間>1s
func BenchmarkFib(b *testing.B) {
for i := 0; i < b.N; i++ {
time.Sleep(time.Second)
fib(30)
}
}
可以看到,只執(zhí)行了1次。
benchmarkFib-12 的-12的意思是 GOMAXPROCS數(shù),即cpu核數(shù),可以使用-cpu來指定cpu核數(shù),-cpu=2,3 表示分別使用GOMAXPROCS=2 和GOMAXPROCS=3 進(jìn)行測試。

可以看到測試用例運(yùn)行了兩輪,分別以單核和12核,但我們的測試結(jié)果沒有發(fā)生變化,因?yàn)槲覀兊臏y試內(nèi)容本身是單核的,與多核無緣。
4.提升準(zhǔn)確度
對于性能測試來說,提升測試精度的一個(gè)重要手段是提升測試時(shí)間和測試次數(shù)。我們可以是用-benchtime 和 -count 來達(dá)到目的。
benchmark 默認(rèn)的benchtime是1s,我們指定2s,可以看到執(zhí)行次數(shù)也提升了約1倍。

我們還能直接指定b.N ,即 -benchtime=30x 表示30次

那么count就是測試的輪數(shù)了,-count=2 測試兩輪。

5.內(nèi)存分配情況
前面的測試結(jié)果中,只能看見執(zhí)行的次數(shù)和一次執(zhí)行的時(shí)間,沒有任何與內(nèi)存相關(guān)的信息。加入 -benchmem 就可以看到。

因?yàn)閒ib函數(shù)使用的空間全在棧上,不需要進(jìn)行內(nèi)存分配。
下面測試切片的內(nèi)存分配。創(chuàng)建目錄 bench/cap
cap.go
// 一次性分配空間
func generateWithCap(n int) []int {
rand.Seed(time.Now().UnixNano())
nums := make([]int, 0, n)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}
// 多次分配空間
func generate(n int) []int {
rand.Seed(time.Now().UnixNano())
nums := make([]int, 0)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}cap_test.go
package cap
import "testing"
func BenchmarkGenerateWithCap(b *testing.B) {
for n := 0; n < b.N; n++ {
generateWithCap(1000000)
}
}
func BenchmarkGenerate(b *testing.B) {
for n := 0; n < b.N; n++ {
generate(1000000)
}
}測試結(jié)果:

可以看到:
一次性分配內(nèi)存的切片賦值函數(shù)比多次分配內(nèi)存的切片賦值函數(shù)消耗內(nèi)存更少。
一次性分配內(nèi)存的切片賦值函數(shù)運(yùn)行時(shí)間更少,因?yàn)閮?nèi)存分配需要耗時(shí)間。
6.測試不同的輸入
不同函數(shù)的復(fù)雜度不同,O(1)、O(n)、O(lgn)等,利用 benchmark 驗(yàn)證復(fù)雜度一個(gè)簡單的方式,是構(gòu)造不同的輸入,對剛才的generate函數(shù)構(gòu)建不同的輸入可以達(dá)到這個(gè)目的。
cap_test.go
func benchmarkGenerate(n int, b *testing.B) {
for i := 0; i < b.N; i++ {
generate(n)
}
}
func BenchmarkGenerate1000(b *testing.B) { benchmarkGenerate(1000, b) }
func BenchmarkGenerate10000(b *testing.B) { benchmarkGenerate(10000, b) }
func BenchmarkGenerate100000(b *testing.B) { benchmarkGenerate(100000, b) }
func BenchmarkGenerate1000000(b *testing.B) { benchmarkGenerate(1000000, b) }隨著輸入按10倍的速度增長,運(yùn)行時(shí)間也按10倍在增長,則函數(shù)的復(fù)雜度是線性的,即O(n).

二、benchmark的注意事項(xiàng)
1.ResetTimer
如果在正式執(zhí)行測試前需要進(jìn)行準(zhǔn)備工作,那么在準(zhǔn)備工作完成后,可以使用b.ResetTimer() 函數(shù)來重置計(jì)數(shù)器。
使用sleep模擬耗時(shí)的準(zhǔn)備工作。
fib_test.go
func BenchmarkFib(b *testing.B) {
time.Sleep(time.Second)
for i := 0; i < b.N; i++ {
fib(30)
}
}
每次執(zhí)行fib(30)高達(dá)1s多,顯然不對。
使用b.ResetTimer()
fib_test.go
func BenchmarkFib(b *testing.B) {
time.Sleep(time.Second)
b.ResetTimer()
for i := 0; i < b.N; i++ {
fib(30)
}
}
我們將耗時(shí)的準(zhǔn)備工作排除在測試之外,每次調(diào)用fib(30)花費(fèi) 6ms = 0.006s
2.StopTimer & StartTimer
如果在每一次函數(shù)前后都需要準(zhǔn)備工作和清理工作,那么就需要StopTimer + StartTimer 函數(shù)了。
例:
sort_test.go
// 一次性分配空間
func generateWithCap(n int) []int {
rand.Seed(time.Now().UnixNano())
nums := make([]int, 0, n)
for i := 0; i < n; i++ {
nums = append(nums, rand.Int())
}
return nums
}
//冒泡排序
func bubbleSort(nums []int) {
for i := 0; i < len(nums); i++ {
for j := 1; j < len(nums)-i; j++ {
if nums[j] < nums[j-1] {
nums[j], nums[j-1] = nums[j-1], nums[j]
}
}
}
}
func BenchmarkBubbleSort(b *testing.B) {
for n := 0; n < b.N; n++ {
//暫停計(jì)時(shí)
b.StopTimer()
nums := generateWithCap(10000)
//繼續(xù)計(jì)時(shí)
b.StartTimer()
bubbleSort(nums)
}
}
顯然我們只測試到排序的性能,沒有將內(nèi)存分配的時(shí)間花費(fèi)算入結(jié)果。
每次排序需花費(fèi)120ms的時(shí)間。
到此這篇關(guān)于go benchmark 基準(zhǔn)測試的文章就介紹到這了,更多相關(guān)go 基準(zhǔn)測試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang如何使用gomobile進(jìn)行Android開發(fā)
golang可以開發(fā)android,使用golang開發(fā)android需要下載安裝gomobile,下面這篇文章主要給大家介紹了關(guān)于golang如何使用gomobile進(jìn)行Android開發(fā)的相關(guān)資料,需要的朋友可以參考下2023-01-01
淺談Go連接池的設(shè)計(jì)與實(shí)現(xiàn)
本文主要介紹了淺談Go連接池的設(shè)計(jì)與實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
golang 進(jìn)度條功能實(shí)現(xiàn)示例
這篇文章主要介紹了golang 進(jìn)度條功能實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
詳解Go操作supervisor xml rpc接口及注意事項(xiàng)
這篇文章主要介紹了Go操作supervisor xml rpc接口及注意事項(xiàng),管理web,在配置文件中配置相關(guān)信息,通過go-supervisor的處理庫進(jìn)行操作,需要的朋友可以參考下2021-09-09

