日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

如何在 GO 中寫出準確的基準測試

來源: 責編: 時間:2023-11-28 09:36:41 235觀看
導讀一般來說,我們不應該對性能進行猜測。在編寫優化時,會有許多因素可能起作用,即使我們對結果有很強的看法,測試它們很少是一個壞主意。然而,編寫基準測試并不簡單。很容易編寫不準確的基準測試,并且基于這些測試得出錯誤的假

一般來說,我們不應該對性能進行猜測。在編寫優化時,會有許多因素可能起作用,即使我們對結果有很強的看法,測試它們很少是一個壞主意。然而,編寫基準測試并不簡單。很容易編寫不準確的基準測試,并且基于這些測試得出錯誤的假設。這篇文章的目標是探討導致不準確的四個常見和具體陷阱:FFZ28資訊網——每日最新資訊28at.com

  • 不重置或暫停計時器
  • 對微基準測試做出錯誤假設
  • 不注意編譯器優化
  • 被觀察效應所誤導

FFZ28資訊網——每日最新資訊28at.com

通用概念

在討論這些陷阱之前,讓我們簡要回顧一下 Go 語言中基準測試的工作原理。基準測試的框架大致如下:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkFoo(b *testing.B) {    for i := 0; i < b.N; i++ {        foo()    }}

函數名以Benchmark前綴開頭。被測試的函數(foo)在for循環內被調用。b.N代表著可變數量的迭代次數。在運行基準測試時,Go 會嘗試使其匹配所請求的基準測試時間。基準測試時間默認設置為1秒,可以使用-benchtime標志進行更改。b.N從1開始;如果基準測試在1秒內完成,b.N會增加,然后再次運行基準測試,直到b.N大致匹配benchtime為止。FFZ28資訊網——每日最新資訊28at.com

$ go test -bench=.cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkFoo-4                73          16511228 ns/op

在這里,基準測試大約花費了1秒鐘,foo被執行了73次,平均執行時間為16,511,228納秒。我們可以使用-benchtime來更改基準測試時間:FFZ28資訊網——每日最新資訊28at.com

$ go test -bench=. -benchtime=2sBenchmarkFoo-4               150          15832169 ns/op

foo 的執行次數大約是上一個基準測試的兩倍。FFZ28資訊網——每日最新資訊28at.com

接下來,讓我們看一些常見的陷阱。FFZ28資訊網——每日最新資訊28at.com

不重置或暫停計時器

在某些情況下,我們需要在基準測試循環之前執行一些操作。這些操作可能需要相當長的時間(例如,生成一個大型數據切片),可能會對基準測試結果產生顯著影響:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkFoo(b *testing.B) {    expensiveSetup()    for i := 0; i < b.N; i++ {        functionUnderTest()    }}

在這種情況下,我們可以在進入循環之前使用ResetTimer方法:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkFoo(b *testing.B) {    expensiveSetup()    b.ResetTimer() // Reset the benchmark timer    for i := 0; i < b.N; i++ {        functionUnderTest()    }}

調用ResetTimer會將自測試開始以來的經過時間和內存分配計數器歸零。這樣,一個昂貴的設置步驟可以從測試結果中排除。FFZ28資訊網——每日最新資訊28at.com

如果我們不僅需要在測試中執行一次昂貴的設置步驟,而是需要在每個循環迭代中都執行呢?FFZ28資訊網——每日最新資訊28at.com

func BenchmarkFoo(b *testing.B) {    for i := 0; i < b.N; i++ {        expensiveSetup()        functionUnderTest()    }}

我們不能重置計時器,因為那樣會在每次循環迭代期間執行。但是我們可以停止和恢復基準測試計時器,將調用expensiveSetup包裹起來:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkFoo(b *testing.B) {    for i := 0; i < b.N; i++ {        b.StopTimer() // Pause the benchmark timer        expensiveSetup()        b.StartTimer() // Resume the benchmark timer        functionUnderTest()    }}

在這里,我們暫停基準測試計時器來執行昂貴的設置步驟,然后恢復計時器。FFZ28資訊網——每日最新資訊28at.com

注意: 關于這種方法有一個需要記住的地方:如果被測試的函數執行速度遠遠比設置函數要快,那么基準測試可能會花費太長時間才能完成。原因是它需要比 benchtime 更長的時間才能完成。基準測試時間的計算完全基于 functionUnderTest 的執行時間。所以,如果在每個循環迭代中等待了相當長的時間,基準測試就會比1秒要慢得多。如果我們想保持基準測試,一種可能的緩解方法是減少 benchtime 。FFZ28資訊網——每日最新資訊28at.com

我們必須確保使用計時器方法來保持基準測試的準確性。FFZ28資訊網——每日最新資訊28at.com

對微基準測試做出錯誤假設

微基準測試測量的是微小的計算單元,很容易對它做出錯誤的假設。比如說,我們不確定是應該使用atomic.StoreInt32還是atomic.StoreInt64(假設處理的值始終適合32位)。我們想編寫一個基準測試來比較這兩個函數:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkAtomicStoreInt32(b *testing.B) {    var v int32    for i := 0; i < b.N; i++ {        atomic.StoreInt32(&v, 1)    }}func BenchmarkAtomicStoreInt64(b *testing.B) {    var v int64    for i := 0; i < b.N; i++ {        atomic.StoreInt64(&v, 1)    }}

如果我們運行這個基準測試,以下是一些示例輸出:FFZ28資訊網——每日最新資訊28at.com

cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkAtomicStoreInt32BenchmarkAtomicStoreInt32-4    197107742           5.682 ns/opBenchmarkAtomicStoreInt64BenchmarkAtomicStoreInt64-4    213917528           5.134 ns/op

我們很容易認為這個基準測試并且決定使用atomic.StoreInt64,因為它看起來更快。現在,為了進行一次公平的基準測試,我們改變順序,先測試atomic.StoreInt64,然后測試atomic.StoreInt32。以下是一些示例輸出:FFZ28資訊網——每日最新資訊28at.com

BenchmarkAtomicStoreInt64BenchmarkAtomicStoreInt64-4    224900722           5.434 ns/opBenchmarkAtomicStoreInt32BenchmarkAtomicStoreInt32-4    230253900           5.159 ns/op

這次,atomic.StoreInt32有更好的結果。發生了什么事?FFZ28資訊網——每日最新資訊28at.com

在微基準測試中,許多因素都會影響結果,比如在運行基準測試時的機器活動、電源管理、熱管理和一系列指令的更好的緩存對齊。我們必須記住,許多因素,甚至超出我們 Go 項目范圍的因素,都可能會影響結果。FFZ28資訊網——每日最新資訊28at.com

注意: 我們應該確保運行基準測試的機器處于空閑狀態。但是,外部進程可能在后臺運行,這可能會影響基準測試結果。因此,像 perflock 這樣的工具可以限制基準測試可以消耗的CPU。例如,我們可以使用總可用CPU的70%來運行基準測試,將30%分配給操作系統和其他進程,減少機器活動因素對結果的影響。FFZ28資訊網——每日最新資訊28at.com

一個選擇是使用-benchtime選項增加基準測試時間。類似于概率論中的大數定律,如果我們運行大量次數的基準測試,它應該趨向于接近其期望值(假設我們忽略了指令緩存等機制的好處)。FFZ28資訊網——每日最新資訊28at.com

另一個選擇是在經典基準測試工具之上使用外部工具。例如,benchstat工具是golang.org/x代碼庫的一部分,它允許我們計算和比較有關基準測試執行的統計數據。FFZ28資訊網——每日最新資訊28at.com

讓我們使用-count選項運行基準測試10次,并將輸出重定向到一個特定的文件:FFZ28資訊網——每日最新資訊28at.com

$ go test -bench=. -count=10 | tee stats.txtcpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkAtomicStoreInt32-4     234935682                5.124 ns/opBenchmarkAtomicStoreInt32-4     235307204                5.112 ns/op// ...BenchmarkAtomicStoreInt64-4     235548591                5.107 ns/opBenchmarkAtomicStoreInt64-4     235210292                5.090 ns/op// ...

然后我們可以在這個文件上運行benchstat:FFZ28資訊網——每日最新資訊28at.com

$ benchstat stats.txtname                time/opAtomicStoreInt32-4  5.10ns ± 1%AtomicStoreInt64-4  5.10ns ± 1%

結果是一樣的:兩個函數的平均完成時間都是5.10納秒。我們還可以看到給定基準測試執行間的百分比變化:±1%。這個指標告訴我們,兩個基準測試都是穩定的,這讓我們對計算出的平均結果更有信心。因此,我們不能得出atomic.StoreInt32比atomic.StoreInt64快或慢的結論,而是可以得出對于我們測試的用途(在特定的Go版本和特定的機器上),它們的執行時間是相似的。FFZ28資訊網——每日最新資訊28at.com

總的來說,我們在進行微基準測試時應該保持謹慎。許多因素都可以對結果產生重大影響,并潛在地導致錯誤的假設。增加基準測試時間或重復基準測試的執行,并使用諸如benchstat之類的工具計算統計數據,可以是限制外部因素并獲得更準確結果的有效方法,從而得出更好的結論。FFZ28資訊網——每日最新資訊28at.com

我們還應該注意,如果另一個系統最終運行該應用程序,要小心使用在特定機器上執行的微基準測試的結果。生產系統的行為可能與我們運行微基準測試的系統大不相同。FFZ28資訊網——每日最新資訊28at.com

不注意編譯器優化

與編寫基準測試相關的另一個常見錯誤是被編譯器優化所欺騙,這也可能導致錯誤的基準測試假設。在這一節中,我們將看看Go語言的14813號問題(https://github.com/golang/go/issues/14813,也被Go項目成員Dave Cheney討論過),涉及到一個人口統計函數(一個計算設置為1的位數的函數):FFZ28資訊網——每日最新資訊28at.com

const m1 = 0x5555555555555555const m2 = 0x3333333333333333const m4 = 0x0f0f0f0f0f0f0f0fconst h01 = 0x0101010101010101func popcnt(x uint64) uint64 {    x -= (x >> 1) & m1    x = (x & m2) + ((x >> 2) & m2)    x = (x + (x >> 4)) & m4    return (x * h01) >> 56}

這個函數接受一個uint64并返回一個uint64。為了對這個函數進行基準測試,我們可以編寫以下內容:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkPopcnt1(b *testing.B) {    for i := 0; i < b.N; i++ {        popcnt(uint64(i))    }}

然而,如果我們執行這個基準測試,結果會非常低得令人驚訝:FFZ28資訊網——每日最新資訊28at.com

cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkPopcnt1-4      1000000000               0.2858 ns/op

一個持續時間為0.28納秒大約是一個時鐘周期,所以這個數字是非常不合理地低。問題在于開發者對編譯器優化不夠謹慎。在這種情況下,被測試的函數非常簡單,可以成為內聯的候選對象:一種優化,用被調用函數的主體替換函數調用,讓我們避免函數調用,這具有很小的開銷。一旦函數被內聯,編譯器注意到調用沒有副作用,并用以下基準測試替換了它:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkPopcnt1(b *testing.B) {    for i := 0; i < b.N; i++ {        // Empty    }}

現在基準測試是空的,這就是我們得到接近一個時鐘周期結果的原因。為了防止這種情況發生,最佳實踐是遵循以下模式:FFZ28資訊網——每日最新資訊28at.com

  • 在每個循環迭代中,將結果賦值給一個局部變量(在基準測試函數的上下文中為局部變量)。
  • 將最新的結果賦值給一個全局變量。

在我們的情況下,我們編寫以下基準測試:FFZ28資訊網——每日最新資訊28at.com

var global uint64 // Define a global variablefunc BenchmarkPopcnt2(b *testing.B) {    var v uint64 // Define a local variable    for i := 0; i < b.N; i++ {        v = popcnt(uint64(i)) // Assign the result to the local variable    }    global = v // Assign the result to the global variable}

global 是一個全局變量,而 v 是一個局部變量,其作用域限定在基準測試函數內部。在每個循環迭代中,我們將 popcnt 的結果賦值給局部變量 v。然后將最新的結果賦值給全局變量 global。FFZ28資訊網——每日最新資訊28at.com

如果我們運行這兩個基準測試,現在結果之間會有顯著的差異:FFZ28資訊網——每日最新資訊28at.com

cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkPopcnt1-4      1000000000               0.2858 ns/opBenchmarkPopcnt2-4      606402058                1.993 ns/op

BenchmarkPopcnt2 是基準測試的準確版本。它確保我們避免了內聯優化,這種優化可能會人為降低執行時間,甚至去除對被測試函數的調用。依賴BenchmarkPopcnt1的結果可能會導致錯誤的假設。FFZ28資訊網——每日最新資訊28at.com

讓我們記住避免編譯器優化誤導基準測試結果的模式:將被測試函數的結果賦值給一個局部變量,然后將最新的結果賦值給一個全局變量。這種最佳實踐還可以防止我們做出不正確的假設。FFZ28資訊網——每日最新資訊28at.com

被觀察效應的欺騙

在物理學中,觀察者效應是通過觀察行為干擾觀察系統的現象。這種效應也可以在基準測試中看到,并可能導致對結果做出錯誤的假設。讓我們看一個具體的例子,然后嘗試減輕這種影響。FFZ28資訊網——每日最新資訊28at.com

我們想要實現一個接收int64元素矩陣的函數。這個矩陣有固定的512列,我們想要計算前八列的總和,如圖11.2所示。FFZ28資訊網——每日最新資訊28at.com

FFZ28資訊網——每日最新資訊28at.com

計算前八列的總和FFZ28資訊網——每日最新資訊28at.com

為了優化的目的,我們還想確定變化列數是否會產生影響,所以我們也實現了一個有513列的第二個函數。實現如下:FFZ28資訊網——每日最新資訊28at.com

func calculateSum512(s [][512]int64) int64 {    var sum int64    for i := 0; i < len(s); i++ { // Iterate over each row        for j := 0; j < 8; j++ { // Iterate over the first eight columns            sum += s[i][j] // Increment sum        }    }    return sum}func calculateSum513(s [][513]int64) int64 {    // Same implementation as calculateSum512}

我們遍歷每一行,然后遍歷前八列,并增加一個我們要返回的總和變量。在calculateSum513中的實現保持不變。FFZ28資訊網——每日最新資訊28at.com

我們想對這些函數進行基準測試,以決定在固定行數的情況下哪一個性能最好:FFZ28資訊網——每日最新資訊28at.com

const rows = 1000var res int64func BenchmarkCalculateSum512(b *testing.B) {    var sum int64    s := createMatrix512(rows) // Create a matrix of 512 columns    b.ResetTimer()    for i := 0; i < b.N; i++ {        sum = calculateSum512(s) // Create a matrix of 512 columns    }    res = sum}func BenchmarkCalculateSum513(b *testing.B) {    var sum int64    s := createMatrix513(rows) // Create a matrix of 513 columns    b.ResetTimer()    for i := 0; i < b.N; i++ {        sum = calculateSum513(s) // Calculate the sum    }    res = sum}

我們希望只創建一次矩陣,以限制對結果的影響。因此,我們在循環外調用createMatrix512和createMatrix513。我們可能期望結果會類似,因為我們只想遍歷前八列,但事實并非如此(在我的機器上):FFZ28資訊網——每日最新資訊28at.com

cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkCalculateSum512-4        81854             15073 ns/opBenchmarkCalculateSum513-4       161479              7358 ns/op

第二個擁有513列的基準測試要快大約50%。再次強調,因為我們只遍歷了前八列,這個結果相當令人驚訝。FFZ28資訊網——每日最新資訊28at.com

要理解這種差異,我們需要了解CPU緩存的基礎知識。簡而言之,CPU由不同的緩存組成(通常是L1、L2和L3)。這些緩存降低了從主存儲器訪問數據的平均成本。在某些條件下,CPU可以從主存儲器獲取數據并將其復制到L1。在這種情況下,CPU試圖將calculateSum感興趣的矩陣子集(每行的前八列)放入L1。然而,一個情況下矩陣可以完全放入內存(513列),而另一個情況下不能(512列)。FFZ28資訊網——每日最新資訊28at.com

回到基準測試,主要問題在于我們在兩種情況下都重復使用相同的矩陣。因為該函數重復執行數千次,我們并未測量接收純新矩陣的函數執行情況。相反,我們測量的是一個已經包含在緩存中的矩陣的子集的函數。因此,由于calculateSum513導致的緩存未命中更少,它具有更好的執行時間。FFZ28資訊網——每日最新資訊28at.com

這是觀察效應的一個例子。因為我們一直在觀察重復調用的CPU密集型函數,CPU緩存可能會起作用,并且會對結果產生顯著影響。在這個例子中,為了防止這種效應,我們應該在每次測試中創建一個新的矩陣,而不是重復使用一個:FFZ28資訊網——每日最新資訊28at.com

func BenchmarkCalculateSum512(b *testing.B) {    var sum int64    for i := 0; i < b.N; i++ {        b.StopTimer()        s := createMatrix512(rows) // Create a new matrix during each loop iteration        b.StartTimer()        sum = calculateSum512(s)    }    res = sum}

現在在每次循環迭代中都創建了一個新的矩陣。如果我們再次運行基準測試(并調整benchtime——否則執行時間太長),結果會更接近:FFZ28資訊網——每日最新資訊28at.com

cpu: Intel(R) Core(TM) i5-7360U CPU @ 2.30GHzBenchmarkCalculateSum512-4         1116             33547 ns/opBenchmarkCalculateSum513-4          998             35507 ns/op

不是做出calculateSum513更快的錯誤假設,我們看到當接收新矩陣時,兩個基準測試的結果更接近。FFZ28資訊網——每日最新資訊28at.com

正如我們在本文中看到的那樣,因為我們重復使用了同一個矩陣,CPU緩存對結果產生了顯著影響。為了防止這種情況發生,我們必須在每個循環迭代期間創建一個新的矩陣。總的來說,我們應該記住,在觀察一個被測試函數的情況下,可能會在結果中產生顯著差異,特別是在涉及低級優化的CPU密集型函數的微基準測試中。強制基準測試在每次迭代期間重新創建數據可能是防止這種影響的一種好方法。FFZ28資訊網——每日最新資訊28at.com

結論

  • 使用時間方法來保持基準測試的準確性。
  • 當處理微基準測試時,增加benchtime或使用benchstat等工具可能會有所幫助。
  • 注意微基準測試的結果,如果最終運行應用程序的系統與運行微基準測試的系統不同。
  • 確保被測試函數產生了副作用,以防止編譯器優化讓您對基準測試結果產生誤解。
  • 為了防止觀察者效應,強制基準測試重新創建CPU密集型函數使用的數據。

本文鏈接:http://www.www897cc.com/showinfo-26-34662-0.html如何在 GO 中寫出準確的基準測試

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Prometheus 的查詢語言 PromQL 詳解

下一篇: 深入解析PyQt 6:為什么它比PyQt 5更強大?

標簽:
  • 熱門焦點
  • K60至尊版剛預熱 一加Ace2 Pro正面硬剛

    Redmi這邊剛如火如荼的宣傳了K60 Ultra的各種技術和硬件配置,作為競品的一加也坐不住了。一加中國區總裁李杰發布了兩條微博,表示在自家的一加Ace2上早就已經采用了和PixelWo
  • 7月安卓手機性價比榜:努比亞+紅魔兩款新機入榜

    7月登場的新機有努比亞Z50S Pro和紅魔8S Pro,除了三星之外目前唯二的兩款搭載超頻版驍龍8Gen2處理器的產品,而且努比亞和紅魔也一貫有著不錯的性價比,所以在本次的性價比榜單
  • 一篇聊聊Go錯誤封裝機制

    %w 是用于錯誤包裝(Error Wrapping)的格式化動詞。它是用于 fmt.Errorf 和 fmt.Sprintf 函數中的一個特殊格式化動詞,用于將一個錯誤(或其他可打印的值)包裝在一個新的錯誤中。使
  • 使用LLM插件從命令行訪問Llama 2

    最近的一個大新聞是Meta AI推出了新的開源授權的大型語言模型Llama 2。這是一項非常重要的進展:Llama 2可免費用于研究和商業用途。(幾小時前,swyy發現它已從LLaMA 2更名為Lla
  • 慕巖炮轟抖音,百合網今何在?

    來源:價值研究所 作者:Hernanderz&ldquo;難道就因為自己的一個產品牛逼了,從客服到總裁,都不愿意正視自己產品和運營上的問題,選擇逃避了嗎?&rdquo;這一番話,出自百合網聯合創
  • 電視息屏休眠仍有網絡上傳 愛奇藝被質疑“薅消費者羊毛”

    記者丨寧曉敏 見習生丨汗青出品丨鰲頭財經(theSankei) 前不久,愛奇藝發布了一份亮眼的一季報,不僅營收和會員營收創造歷史最佳表現,其運營利潤也連續6個月實現增長。自去年年初
  • 華為HarmonyOS 4升級計劃公布:首批34款機型今日開啟公測

    8月4日消息,今天下午華為正式發布了HarmonyOS 4系統,在更流暢的前提下,還帶來了不少新功能,UI設計也有變化,會讓手機煥然一新。華為宣布,首批機型將會在
  • 2納米決戰2025

    集微網報道 從三強爭霸到四雄逐鹿,2nm的廝殺聲已然隱約傳來。無論是老牌勁旅臺積電、三星,還是誓言重回先進制程領先地位的英特爾,甚至初成立不久的新
  • 回歸OPPO兩年,一加贏了銷量,輸了品牌

    成為OPPO旗下主打性能的先鋒品牌后,一加屢創佳績。今年618期間,一加手機全渠道銷量同比增長362%,憑借一加 11、一加 Ace 2、一加 Ace 2V三款爆品,一加
Top 主站蜘蛛池模板: 霍州市| 即墨市| 吉林省| 铜陵市| 土默特左旗| 泗水县| 淮阳县| 阿坝县| 信丰县| 洪江市| 潜山县| 中宁县| 巩义市| 明光市| 家居| 竹溪县| 澄城县| 永丰县| 桃源县| 阿克陶县| 大田县| 呈贡县| 阿巴嘎旗| 开原市| 册亨县| 南投县| 观塘区| 西青区| 互助| 定州市| 唐河县| 湖口县| 云和县| 甘德县| 桂林市| 泉州市| 玛纳斯县| 鄂伦春自治旗| 胶南市| 岑巩县| 吉木萨尔县|