go benchmark 效能測試
go 效能測試
基準測試
基準測試主要是通過測試CPU和記憶體的效率問題,來評估被測試程式碼的效能,進而找到更好的解決方案。
編寫基準測試
func BenchmarkSprintf(b *testing.B){ num:=10 b.ResetTimer() for i:=0;i<b.N;i++{ fmt.Sprintf("%d",num) } }
- 基準測試的程式碼檔案必須以_test.go結尾
- 基準測試的函式必須以Benchmark開頭,必須是可匯出的
- 基準測試函式必須接受一個指向Benchmark型別的指標作為唯一引數
- 基準測試函式不能有返回值
- b.ResetTimer是重置計時器,這樣可以避免for迴圈之前的初始化程式碼的干擾
- 最後的for迴圈很重要,被測試的程式碼要放到迴圈裡
- b.N是基準測試框架提供的,表示迴圈的次數,因為需要反覆呼叫測試的程式碼,才可以評估效能
➜ go test -bench=. -run=none BenchmarkSprintf-8 20000000 117 ns/op PASS ok flysnow.org/hello 2.474s
使用 go test
命令,加上 -bench=
標記,接受一個表示式作為引數, .
表示執行所有的基準測試
因為預設情況下 go test
會執行單元測試,為了防止單元測試的輸出影響我們檢視基準測試的結果,可以使用-run=
匹配一個從來沒有的單元測試方法,過濾掉單元測試的輸出,我們這裡使用none
,因為我們基本上不會建立這個名字的單元測試方法。
也可以使用 -run=^$
, 匹配這個規則的,但是沒有,所以只會執行benchmark
go test -bench=. -run=^$
有些時候在benchmark之前需要做一些準備工作,並且,我們不希望這些準備工作納入到計時裡面,我們可以使用 b.ResetTimer(),代表重置計時為0,以呼叫時的時刻作為重新計時的開始。
看到函式後面的-8
了嗎?這個表示執行時對應的GOMAXPROCS的值。
接著的20000000
表示執行for迴圈的次數也就是呼叫被測試程式碼的次數
最後的117 ns/op
表示每次需要話費117納秒。(執行一次操作話費的時間)
以上是測試時間預設是1秒,也就是1秒的時間,呼叫兩千萬次,每次呼叫花費117納秒。
如果想讓測試執行的時間更長,可以通過-benchtime指定,比如3秒。
➜ hello go test -bench=. -benchtime=3s -run=none
// Benchmark 名字 - CPU 迴圈次數 平均每次執行時間
BenchmarkSprintf-8 50000000 109 ns/op
PASS
// 哪個目錄下執行go test 累計耗時
ok flysnow.org/hello 5.628s
可以發現,我們加長了測試時間,測試的次數變多了,但是最終的效能結果:每次執行的時間,並沒有太大變化。一般來說這個值最好不要超過3秒,意義不大。
效能對比
上面那個基準測試的例子,其實是一個int型別轉為string型別的例子,標準庫裡還有幾種方法,我們看下哪種效能更加.
func BenchmarkSprintf(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
fmt.Sprintf("%d",num)
}
}
func BenchmarkFormat(b *testing.B){
num:=int64(10)
b.ResetTimer()
for i:=0;i<b.N;i++{
strconv.FormatInt(num,10)
}
}
func BenchmarkItoa(b *testing.B){
num:=10
b.ResetTimer()
for i:=0;i<b.N;i++{
strconv.Itoa(num)
}
}
➜ hello go test -bench=. -run=none
BenchmarkSprintf-8 20000000 117 ns/op
BenchmarkFormat-8 50000000 33.3 ns/op
BenchmarkItoa-8 50000000 34.9 ns/op
PASS
ok flysnow.org/hello 5.951s
從結果上看strconv.FormatInt
函式是最快的,其次是strconv.Itoa
,然後是fmt.Sprintf
最慢,前兩個函式效能達到了最後一個的3倍多。那麼最後一個為什麼這麼慢的,我們再通過-benchmem
找到根本原因。
➜ hello go test -bench=. -benchmem -run=none
BenchmarkSprintf-8 20000000 110 ns/op 16 B/op 2 allocs/op
BenchmarkFormat-8 50000000 31.0 ns/op 2 B/op 1 allocs/op
BenchmarkItoa-8 50000000 33.1 ns/op 2 B/op 1 allocs/op
PASS
ok flysnow.org/hello 5.610s
-benchmem
可以提供每次操作分配記憶體的次數,以及每次操作分配的位元組數。從結果我們可以看到,效能高的兩個函式,每次操作都是進行1次記憶體分配,而最慢的那個要分配2次;效能高的每次操作分配2個位元組記憶體,而慢的那個函式每次需要分配16位元組的記憶體。從這個資料我們就知道它為什麼這麼慢了,記憶體分配都佔用都太高。
在程式碼開發中,對於我們要求效能的地方,編寫基準測試非常重要,這有助於我們開發出效能更好的程式碼。不過效能、可用性、複用性等也要有一個相對的取捨,不能為了追求效能而過度優化。
結合 pprof
pprof 效能監控
package bench
import "testing"
func Fib(n int) int {
if n < 2 {
return n
}
return Fib(n-1) + Fib(n-2)
}
func BenchmarkFib10(b *testing.B) {
// run the Fib function b.N times
for n := 0; n < b.N; n++ {
Fib(10)
}
}
go test -bench=. -benchmem -cpuprofile profile.out
還可以同時看記憶體
go test -bench=. -benchmem -memprofile memprofile.out -cpuprofile profile.out
然後就可以用輸出的檔案使用pprof
go tool pprof profile.out
File: bench.test
Type: cpu
Time: Apr 5, 2018 at 4:27pm (EDT)
Duration: 2s, Total samples = 1.85s (92.40%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) top
Showing nodes accounting for 1.85s, 100% of 1.85s total
flat flat% sum% cum cum%
1.85s 100% 100% 1.85s 100% bench.Fib
0 0% 100% 1.85s 100% bench.BenchmarkFib10
0 0% 100% 1.85s 100% testing.(*B).launch
0 0% 100% 1.85s 100% testing.(*B).runN
這個是使用cpu 檔案, 也可以使用記憶體檔案
然後你也可以用list命令檢查函式需要的時間
(pprof) list Fib
1.84s 2.75s (flat, cum) 148.65% of Total
. . 1:package bench
. . 2:
. . 3:import "testing"
. . 4:
530ms 530ms 5:func Fib(n int) int {
260ms 260ms 6: if n < 2 {
130ms 130ms 7: return n
. . 8: }
920ms 1.83s 9: return Fib(n-1) + Fib(n-2)
. . 10:}
或者使用web命令生成影象(png,pdf,...)
報錯:Failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in %PATH%
是你電腦沒有安裝gvedit導致的
fq進入gvedit官網https://graphviz.gitlab.io/_pages/Download/Download_windows.html 下載穩定版
mac 安裝, 安裝好後就可以使用web進行展現了
brew install graphviz
Testing flags
go 測試後面可以跟哪些引數
常用flag
- -bench regexp:效能測試,支援表示式對測試函式進行篩選。-bench .則是對所有的benchmark函式測試
- -benchmem:效能測試的時候顯示測試函式的記憶體分配的統計資訊
- -count n:執行測試和效能多少此,預設一次
- -run regexp:只執行特定的測試函式, 比如-run ABC只測試函式名中包含ABC的測試函式
- -timeout t:測試時間如果超過t, panic,預設10分鐘
- -v:顯示測試的詳細資訊,也會把Log、Logf方法的日誌顯示出來
Go 1.7中開始支援 sub-test的概念。
測試注意和調優
- 避免頻繁呼叫timer
- 避免測試資料過大
參考
Profile your golang benchmark with pprof
PS: 覺得不錯的請點個贊吧!!