Go性能測試


 

性能測試函數以Benchmark開頭,b  *testing.B為參數, b.N為執行次數,次數不是固定的,是一秒內能執行的次數,不同的函數 次數不一樣

split.go

package split

import (
    "strings"
)

func Split(s, sep string) (result []string) {
    i := strings.Index(s, sep)

    for i > -1 {
        result = append(result, s[:i])
        s = s[i+len(sep):] // 這里使用len(sep)獲取sep的長度
        i = strings.Index(s, sep)
    }
    result = append(result, s)
    return
}

split_test.go

package split_test

import (
    "github.com/business_group/test_project/split"
    "testing"
)

func BenchmarkSplit(b *testing.B) {
    for i := 0; i < b.N; i++ {
        split.Split("a,b,c,d,e,f", ",")
    }
}

執行性能測試:`go  test   -bench=Split`

 

 其中BenchmarkSplit-4表示對Split函數進行基准測試,數字4表示GOMAXPROCS的值,這個對於並發基准測試很重要。3045511402ns/op表示每次調用Split函數耗時402ns,這個結果是3045511次調用的平均值。

查看內存占用情況:`go test -bench=Split -benchmem`

 

 其中,240 B/op表示每次操作內存分配了240字節,4 allocs/op則表示每次操作進行了4次內存分配。 我們將我們的Split函數優化如下:

func Split(s, sep string) (result []string) { result = make([]string, 0, strings.Count(s, sep)+1) i := strings.Index(s, sep) for i > -1 { result = append(result, s[:i]) s = s[i+len(sep):] // 這里使用len(sep)獲取sep的長度 i = strings.Index(s, sep) } result = append(result, s) return }

這一次我們提前使用make函數將result初始化為一個容量足夠大的切片,而不像之前一樣append函數來追加。我們來看一下這個改進會帶來多大的性能提升:

 

 

性能比較函數(重要)

有的函數,跑10次、100次沒問題,但跑100萬次,1000萬次會有性能的顯著下降

例如斐波那契數列,算1的時候很快,算到100就很卡

 

上面的基准測試只能得到給定操作的絕對耗時,但是在很多性能問題是發生在兩個不同操作之間的相對耗時,比如同一個函數處理1000個元素的耗時與處理1萬甚至100萬個元素的耗時的差別是多少?再或者對於同一個任務究竟使用哪種算法性能最佳?我們通常需要對兩個不同算法的實現使用相同的輸入來進行基准比較測試。

性能比較函數通常是一個帶有參數的函數,被多個不同的Benchmark函數傳入不同的值來調用。舉個例子如下:

// fib.go // Fib 是一個計算第n個斐波那契數的函數 func Fib(n int) int { if n < 2 { return n } return Fib(n-1) + Fib(n-2) }

 

性能比較函數:

// fib_test.go 
//性能比較測試 func benchmarkFib(b *testing.B, n int) { for i := 0; i < b.N; i++ { Fib(n) } } func BenchmarkFib1(b *testing.B) { benchmarkFib(b, 1) } func BenchmarkFib2(b *testing.B) { benchmarkFib(b, 2) } func BenchmarkFib3(b *testing.B) { benchmarkFib(b, 3) } func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) } func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) } func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40) }

運行性能基准測試:`go test  -bench=Fib2`

 

 使用`go test -bench=. `運行所有的。默認情況下,每個基准測試至少運行1秒。如果在Benchmark函數返回時沒有到1秒,則b.N的值會按1,2,5,10,20,50,…增加,並且函數再次運行。

 

 如果你想強制執行20s:`go test -bench=Fib40 -benchtime=20s`

 

 

重置時間

b.ResetTimer之前的處理不會放到執行時間里,也不會輸出到報告中,所以可以在之前做一些不計划作為測試報告的操作。例如:

func BenchmarkSplit(b *testing.B) { time.Sleep(5 * time.Second) // 假設需要做一些耗時的無關操作 b.ResetTimer() // 重置計時器 for i := 0; i < b.N; i++ { Split("沙河有沙又有河", "沙") } }

並行測試

func (b *B) RunParallel(body func(*PB))會以並行的方式執行給定的基准測試。

RunParallel會創建出多個goroutine,並將b.N分配給這些goroutine執行, 其中goroutine數量的默認值為GOMAXPROCS。用戶如果想要增加非CPU受限(non-CPU-bound)基准測試的並行性, 那么可以在RunParallel之前調用SetParallelism 。RunParallel通常會與-cpu標志一同使用。

func BenchmarkSplitParallel(b *testing.B) { b.SetParallelism(1) // 設置使用的CPU數 b.RunParallel(func(pb *testing.PB) { for pb.Next() { Split("沙河有沙又有河", "沙") } }) }

執行:`go test -bench=Split

還可以通過在測試命令后添加-cpu參數如`go test -bench=. -cpu 1`來指定使用的CPU數量。

 

Setup與TearDown

測試程序有時需要在測試之前進行額外的設置(setup)或在測試之后進行拆卸(teardown)。

通過在*_test.go文件中定義TestMain函數來可以在測試之前進行額外的設置(setup)或在測試之后進行拆卸(teardown)操作。

如果測試文件包含函數:func TestMain(m *testing.M)那么生成的測試會先調用 TestMain(m),然后再運行具體測試。TestMain運行在主goroutine中, 可以在調用 m.Run前后做任何設置(setup)和拆卸(teardown)。退出測試的時候應該使用m.Run的返回值作為參數調用os.Exit

一個使用TestMain來設置Setup和TearDown的示例如下:

func TestMain(m *testing.M) { fmt.Println("write setup code here...") // 測試之前的做一些設置 // 如果 TestMain 使用了 flags,這里應該加上flag.Parse() retCode := m.Run() // 執行測試 fmt.Println("write teardown code here...") // 測試之后做一些拆卸工作 os.Exit(retCode) // 退出測試 }

需要注意的是:在調用TestMain時, flag.Parse並沒有被調用。所以如果TestMain 依賴於command-line標志 (包括 testing 包的標記), 則應該顯示的調用flag.Parse

子測試的Setup與Teardown

有時候我們可能需要為每個測試集設置Setup與Teardown,也有可能需要為每個子測試設置Setup與Teardown。下面我們定義兩個函數工具函數如下:

// 測試集的Setup與Teardown func setupTestCase(t *testing.T) func(t *testing.T) { t.Log("如有需要在此執行:測試之前的setup") return func(t *testing.T) { t.Log("如有需要在此執行:測試之后的teardown") } } // 子測試的Setup與Teardown func setupSubTest(t *testing.T) func(t *testing.T) { t.Log("如有需要在此執行:子測試之前的setup") return func(t *testing.T) { t.Log("如有需要在此執行:子測試之后的teardown") } }

 

使用方式如下:

func TestSplit(t *testing.T) { type test struct { // 定義test結構體 input string sep string want []string } tests := map[string]test{ // 測試用例使用map存儲 "simple": {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}}, "wrong sep": {input: "a:b:c", sep: ",", want: []string{"a:b:c"}}, "more sep": {input: "abcd", sep: "bc", want: []string{"a", "d"}}, "leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"", "河有", "又有河"}}, } teardownTestCase := setupTestCase(t) // 測試之前執行setup操作 defer teardownTestCase(t) // 測試之后執行testdoen操作 for name, tc := range tests { t.Run(name, func(t *testing.T) { // 使用t.Run()執行子測試 teardownSubTest := setupSubTest(t) // 子測試之前執行setup操作 defer teardownSubTest(t) // 測試之后執行testdoen操作 got := Split(tc.input, tc.sep) if !reflect.DeepEqual(got, tc.want) { t.Errorf("excepted:%#v, got:%#v", tc.want, got) } }) } }

 

測試結果:

 

split $ go test -v === RUN TestSplit === RUN TestSplit/simple === RUN TestSplit/wrong_sep === RUN TestSplit/more_sep === RUN TestSplit/leading_sep --- PASS: TestSplit (0.00s) split_test.go:71: 如有需要在此執行:測試之前的setup --- PASS: TestSplit/simple (0.00s) split_test.go:79: 如有需要在此執行:子測試之前的setup split_test.go:81: 如有需要在此執行:子測試之后的teardown --- PASS: TestSplit/wrong_sep (0.00s) split_test.go:79: 如有需要在此執行:子測試之前的setup split_test.go:81: 如有需要在此執行:子測試之后的teardown --- PASS: TestSplit/more_sep (0.00s) split_test.go:79: 如有需要在此執行:子測試之前的setup split_test.go:81: 如有需要在此執行:子測試之后的teardown --- PASS: TestSplit/leading_sep (0.00s) split_test.go:79: 如有需要在此執行:子測試之前的setup split_test.go:81: 如有需要在此執行:子測試之后的teardown split_test.go:73: 如有需要在此執行:測試之后的teardown === RUN ExampleSplit --- PASS: ExampleSplit (0.00s) PASS ok github.com/Q1mi/studygo/code_demo/test_demo/split 0.006s

示例函數

示例函數用於生成文檔

go test特殊對待的第三種函數就是示例函數,它們的函數名以Example為前綴。它們既沒有參數也沒有返回值。標准格式如下:

func ExampleName() { // ... }


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM