欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

go通過benchmark對代碼進(jìn)行性能測試詳解

 更新時間:2023年04月26日 09:37:38   作者:octobershen  
在開發(fā)中我們要想編寫高性能的代碼,或者優(yōu)化代碼的性能時,你首先得知道當(dāng)前代碼的性能,在go中可以使用testing包的benchmark來做基準(zhǔn)測試 ,文中有詳細(xì)的代碼示例,感興趣的小伙伴可以參考一下

benchmark的使用

在開發(fā)中我們要想編寫高性能的代碼,或者優(yōu)化代碼的性能時,你首先得知道當(dāng)前代碼的性能,在go中可以使用testing包的benchmark來做基準(zhǔn)測試 ,首先我們寫一個簡單的返回隨機字符串的方法

func randomStr(length int) string {
  mathRand.Seed(time.Now().UnixNano())
  letters := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
  b := make([]byte, length)
  for i := range b {
    b[i] = letters[mathRand.Intn(len(letters))]
  }
  return string(b)
}

要對上面的代碼做基準(zhǔn)測試,首先我們要新建一個測試文件,比如main_test.go,然后新建一個基準(zhǔn)測試方法BenchmarkRandomStr,與普通的測試函數(shù)Test 開頭,參數(shù)為t *testing.T類似,基準(zhǔn)測試函數(shù)要以Benchmark開頭,參數(shù)為b *testing.B,代碼中的b.N代表的是該用例的運行次數(shù),這個值是會變的,對于每個用例都不一樣,這個值會從1開始增加,具體的實現(xiàn)我會在下面的實現(xiàn)原理里進(jìn)行介紹。

func BenchmarkRandomStr(b *testing.B) {
  for i := 0; i < b.N; i++ {
    randomStr(10000)
  }
}

運行Benchmark

我們可以使用 go test -bench .命令直接運行當(dāng)前目錄下的所有基準(zhǔn)測試用例,-bench后面也可以跟正則或者是字符串來匹配對應(yīng)的用例

$  go test -bench='Str$'
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-12 ? ? ? ? ? ? ? 6692 ? ? ? ? ? ?181262 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?2.142s
?

對上面的一些關(guān)鍵指標(biāo)我們要了解一下,首先BenchmarkRandomStr-12后面的-12代表的是GOMAXPROCS這個跟你機器CPU的邏輯核數(shù)有關(guān),在基準(zhǔn)測試中可以通過-cpu參數(shù)指定需要以幾核的cpu來運行測試用例

$  go test -bench='Str$' -cpu=2,4,8 .
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-2 ? ? ? ?6715 ? ? ? ? ? ?181197 ns/op
BenchmarkRandomStr-4 ? ? ? ?6471 ? ? ? ? ? ?180249 ns/op
BenchmarkRandomStr-8 ? ? ? ?6616 ? ? ? ? ? ?179510 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?4.516s
?

6715181197 ns/op代表用例執(zhí)行了6715次,每次花費的時間約為0.0001812s,總耗時約為1.2s(ns:s的換算為1000000000:1)

指定測試時長或測試次數(shù)

-benchtime=3s 指定時長

-benchtime=100000x 指定次數(shù)

-coun=3 指定輪數(shù)

$  go test -bench='Str$' -benchtime=3s .
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-12 ? ? ? ? ? ? ?19988 ? ? ? ? ? ?177572 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?5.384s
?
$ go test -bench='Str$' -benchtime=10000x .
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-12 ? ? ? ? ? ? ?10000 ? ? ? ? ? ?184832 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?1.870s
?
$ go test -bench='Str$' -count=2 . 
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-12 ? ? ? ? ? ? ? 6702 ? ? ? ? ? ?177048 ns/op
BenchmarkRandomStr-12 ? ? ? ? ? ? ? 6482 ? ? ? ? ? ?177861 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?3.269s
?
?

重置時間和暫停計時

有時候我們的測試用例會需要一些前置準(zhǔn)備的耗時行為,這對我們的測試結(jié)果會產(chǎn)生影響,這個時候就需要在耗時操作后重置計時。下面我們用一個偽代碼來模擬一下

func BenchmarkRandomStr(b *testing.B) {
  time.Sleep(time.Second * 2) // 模擬耗時操作
  for i := 0; i < b.N; i++ {
    randomStr(10000)
  }
}
?

這時候我們再執(zhí)行一下用例

$ go test -bench='Str$' .
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-12 ? ? ? ? ? ? ? ? ?1 ? ? ? ?2001588866 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?2.009s
?

發(fā)現(xiàn)只執(zhí)行了一次,時間變成了2s多,這顯然不符合我們的預(yù)期,這個時候需要調(diào)用b.ResetTime()來重置時間

func BenchmarkRandomStr(b *testing.B) {
  time.Sleep(time.Second * 2) // 模擬耗時操作
 ?b.ResetTimer() 
  for i := 0; i < b.N; i++ {
    randomStr(10000)
  }
}

再次執(zhí)行基準(zhǔn)測試

$ go test -bench='Str$' .
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkRandomStr-12 ? ? ? ? ? ? ? 6506 ? ? ? ? ? ?183098 ns/op
PASS
ok ? ?  learn/learn_test ? ? ? ?10.030s
?

運行次數(shù)和單次執(zhí)行時間已經(jīng)恢復(fù)到之前測試的情況了?;鶞?zhǔn)測試還有b.StopTimer()b.StartTimer()方法也是同樣的道理,在影響耗時的操作之前停止計時,完成之后再開始計時。

查看內(nèi)存使用情況

我們再評估代碼的性能時,除了時間的快慢,還有一個重要的指標(biāo)就是內(nèi)存使用率,基準(zhǔn)測試中可以通過 -benchmem 來顯示內(nèi)存使用情況。下面我們用一組指定cap和不指定cap的返回int切片方法來看一下內(nèi)存的使用情況

func getIntArr(n int) []int {
  rand.Seed(uint64(time.Now().UnixNano()))
  arr := make([]int, 0)
  for i := 0; i < n; i++ {
    arr = append(arr, rand.Int())
  }
?
  return arr
}
?
func getIntArrWithCap(n int) []int {
  rand.Seed(uint64(time.Now().UnixNano()))
  arr := make([]int, 0, n)
  for i := 0; i < n; i++ {
    arr = append(arr, rand.Int())
  }
?
  return arr
}
//------------------------------------------
// 基準(zhǔn)測試代碼
//------------------------------------------
func BenchmarkGetIntArr(b *testing.B) {
  for i := 0; i < b.N; i++ {
    getIntArr(100000)
  }
}
?
func BenchmarkGetIntArrWithCap(b *testing.B) {
  for i := 0; i < b.N; i++ {
    getIntArrWithCap(100000)
  }
}
?

執(zhí)行基準(zhǔn)測試:

$ go test -bench='Arr' -benchmem .
goos: darwin
goarch: amd64
pkg: learn/learn_test
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
BenchmarkGetIntArr-12 ? ? ? ? ? ? ? ? ? ? ? ?598 ? ? ? ? ? 1928991 ns/op ? ? ? ? 4101389 B/op ? ? ? ? 28 allocs/op
BenchmarkGetIntArrWithCap-12 ? ? ? ? ? ? ? ? 742 ? ? ? ? ? 1556204 ns/op ? ? ? ? ?802817 B/op ? ? ? ? ?1 allocs/op
PASS
ok ? ?  learn/learn_test ? ? ? ?2.688s
?

可以看到指定了cap的方法執(zhí)行的速度大約快20%,而內(nèi)存的使用少了80%左右, 802817 B/op 代表每次的內(nèi)存使用情況,1 allocs/op表示每次操作分配內(nèi)存的次數(shù)

testing.B的底層實現(xiàn)

在寫基準(zhǔn)測試的時候,最讓我搞不懂的是b.N的機制,如何根據(jù)不同的用例來自動調(diào)整執(zhí)行的次數(shù),然后我在源碼中找到了一些蛛絲馬跡。首先,先看一下基準(zhǔn)測試的底層數(shù)據(jù)結(jié)構(gòu)

type B struct {
  common
  importPath ? ? ? string
  context ? ? ? ? ?*benchContext
  N ? ? ? ? ? ? ? ?int // 這個就是要搞懂的N,代表要執(zhí)行的次數(shù)
  previousN ? ? ? ?int ? ? ? ? ?
  previousDuration time.Duration 
  benchFunc ? ? ? ?func(b *B) // 測試函數(shù)
  benchTime ? ? ? ?durationOrCountFlag // 執(zhí)行時間,默認(rèn)是1s 可以通過-benchtime指定
  bytes ? ? ? ? ? ?int64 
  missingBytes ? ? bool 
  timerOn ? ? ? ? ?bool 
  showAllocResult ?bool
  result ? ? ? ? ? BenchmarkResult
  parallelism ? ? ?int 
  
  startAllocs uint64 
  startBytes ?uint64 
  
  netAllocs uint64 
  netBytes ?uint64 
  
  extra map[string]float64
}

通過結(jié)構(gòu)體中的N字段,可以找到幾個關(guān)鍵的方法,runN():每一次執(zhí)行都會調(diào)用的方法,設(shè)置N的值。run1():第一次迭代,根據(jù)它的結(jié)果決定是否需要運行更多的基準(zhǔn)測試。run(): run1()執(zhí)行的結(jié)果為true的情況會調(diào)用,這個方法里調(diào)用doBench()函數(shù)從而調(diào)用launch()函數(shù),這個是最終決定執(zhí)行次數(shù)的函數(shù)

// Run benchmarks f as a subbenchmark with the given name. It reports
// whether there were any failures.
//
// A subbenchmark is like any other benchmark. A benchmark that calls Run at
// least once will not be measured itself and will be called once with N=1.
func (b *B) Run(name string, f func(b *B)) bool {
  // ...省略部分代碼
 ?// Run()方法是基準(zhǔn)測試的啟動方法,會新建一個子測試
  sub := &B{
    common: common{
      signal: ?make(chan bool),
      name: ? ?benchName,
      parent: ?&b.common,
      level: ? b.level + 1,
      creator: pc[:n],
      w: ? ? ? b.w,
      chatty: ?b.chatty,
      bench: ? true,
    },
    importPath: b.importPath,
    benchFunc: ?f,
    benchTime: ?b.benchTime,
    context: ? ?b.context,
  }
// ...省略部分代碼
 ?if sub.run1() { // 執(zhí)行一次子測試,如果不出錯執(zhí)行run()
 ? ?sub.run() //最終調(diào)用 launch()方法,決定需要執(zhí)行多少次runN()
  }
  b.add(sub.result)
  return !sub.failed
}
?
// runN runs a single benchmark for the specified number of iterations.
func (b *B) runN(n int) {
	// ....省略部分代碼
	b.N = n //指定N
	// ...
}

// launch launches the benchmark function. It gradually increases the number
// of benchmark iterations until the benchmark runs for the requested benchtime.
// launch is run by the doBench function as a separate goroutine.
// run1 must have been called on b.
func (b *B) launch() {
 ?// ....省略部分代碼
    d := b.benchTime.d
 ?// 最少執(zhí)行時間為1s,最多執(zhí)行次數(shù)為1e9次
    for n := int64(1); !b.failed && b.duration < d && n < 1e9; {
      last := n
      // 預(yù)測所需要的迭代次數(shù)
      goalns := d.Nanoseconds()
      prevIters := int64(b.N)
      prevns := b.duration.Nanoseconds()
      if prevns <= 0 {
        //四舍五入,預(yù)防除0
        prevns = 1
      }
      n = goalns * prevIters / prevns
 ? ? ?// 避免增長的太快,先按1.2倍增長,最少增加一次
      n += n / 5
      n = min(n, 100*last)
      n = max(n, last+1)
      // 最多執(zhí)行1e9次
      n = min(n, 1e9)
      b.runN(int(n))
}
?

總結(jié)

1.基準(zhǔn)測試方法要以Benchmark開頭

2.執(zhí)行基準(zhǔn)測試用go test -bench .命令執(zhí)行該目錄下所有的基準(zhǔn)測試,-bench后面可以跟正則表達(dá)式,來執(zhí)行符合條件的測試

3.-cpu參數(shù)可以指定運行測試的cpu核心數(shù)

4.-benchtime參數(shù)可以指定運行測試的時間和次數(shù)

5.-count參數(shù)可以指定運行測試的輪數(shù)

6.b.ResetTimer()、b.StopTimer()、b.StartTimer()可以重置或暫停計時,來消除一些耗時操作的影響

以上就是go通過benchmark對代碼進(jìn)行性能測試詳解的詳細(xì)內(nèi)容,更多關(guān)于go benchmark代碼性能測試的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言實現(xiàn)請求超時處理的方法總結(jié)

    Go語言實現(xiàn)請求超時處理的方法總結(jié)

    這篇文章主要為大家詳細(xì)介紹了Go語言中實現(xiàn)請求的超時控制的方法,主要是通過timer和timerCtx來實現(xiàn)請求的超時控制,希望對大家有所幫助
    2023-05-05
  • Go語言基礎(chǔ)go fmt命令使用示例詳解

    Go語言基礎(chǔ)go fmt命令使用示例詳解

    這篇文章主要為大家介紹了Go語言基礎(chǔ)go fmt命令的使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2021-11-11
  • Golang?流水線設(shè)計模式實踐示例詳解

    Golang?流水線設(shè)計模式實踐示例詳解

    這篇文章主要為大家介紹了Golang?流水線設(shè)計模式實踐示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Go分布式鏈路追蹤實戰(zhàn)探索

    Go分布式鏈路追蹤實戰(zhàn)探索

    這篇文章主要為大家介紹了Go分布式鏈路追蹤實戰(zhàn)示例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Go 每日一庫之termtables的使用

    Go 每日一庫之termtables的使用

    本文主要介紹了Go 每日一庫之termtables的使用,termtables處理表格形式數(shù)據(jù)的輸出。是一個很小巧的工具庫。具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • golang不到30行代碼實現(xiàn)依賴注入的方法

    golang不到30行代碼實現(xiàn)依賴注入的方法

    這篇文章主要介紹了golang不到30行代碼實現(xiàn)依賴注入的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • Go語言基于viper實現(xiàn)apollo多實例快速

    Go語言基于viper實現(xiàn)apollo多實例快速

    viper是適用于go應(yīng)用程序的配置解決方案,這款配置管理神器,支持多種類型、開箱即用、極易上手。本文主要介紹了如何基于viper實現(xiàn)apollo多實例快速接入,感興趣的可以了解一下
    2023-01-01
  • 一文了解Go語言中編碼規(guī)范的使用

    一文了解Go語言中編碼規(guī)范的使用

    這篇文章主要介紹了一文了解Go語言中編碼規(guī)范的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • go語言接口的定義和實現(xiàn)簡單分享

    go語言接口的定義和實現(xiàn)簡單分享

    這篇文章主要介紹了go語言接口的定義和實現(xiàn)簡單分享的相關(guān)資料,需要的朋友可以參考下
    2023-08-08
  • Go并發(fā)與鎖的兩種方式該如何提效詳解

    Go并發(fā)與鎖的兩種方式該如何提效詳解

    如果沒有鎖,在我們的項目中,可能會存在多個goroutine同時操作一個資源(臨界區(qū)),這種情況會發(fā)生競態(tài)問題(數(shù)據(jù)競態(tài)),下面這篇文章主要給大家介紹了關(guān)于Go并發(fā)與鎖的兩種方式該如何提效的相關(guān)資料,需要的朋友可以參考下
    2022-12-12

最新評論