1. 程式人生 > >go benchmark 效能測試

go benchmark 效能測試

今年企業對Java開發的市場需求,你看懂了嗎? >>>   

go 效能測試

基準測試

基準測試主要是通過測試CPU和記憶體的效率問題,來評估被測試程式碼的效能,進而找到更好的解決方案。

編寫基準測試

func BenchmarkSprintf(b *testing.B){
	num:=10
	b.ResetTimer()
	for i:=0;i<b.N;i++{
		fmt.Sprintf("%d",num)
	}
}
  1. 基準測試的程式碼檔案必須以_test.go結尾
  2. 基準測試的函式必須以Benchmark開頭,必須是可匯出的
  3. 基準測試函式必須接受一個指向Benchmark型別的指標作為唯一引數
  4. 基準測試函式不能有返回值
  5. b.ResetTimer是重置計時器,這樣可以避免for迴圈之前的初始化程式碼的干擾
  6. 最後的for迴圈很重要,被測試的程式碼要放到迴圈裡
  7. 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,...)

pprof 影象

報錯: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 測試後面可以跟哪些引數

Testing flags

常用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的概念。

測試注意和調優

golang效能測試與調優

  • 避免頻繁呼叫timer
  • 避免測試資料過大

參考

Go語言實戰筆記(二十一)| Go 單元測試

Go語言實戰筆記(二十二)| Go 基準測試

Profile your golang benchmark with pprof

深入Go語言 - 12

Go單元測試&效能測試

golang效能測試與調優

PS: 覺得不錯的請點個贊吧!!