大家好,我是煎魚。
五一假期時看到 @Ben Hoyt 大佬分享的文章《Go performance from version 1.0 to 1.22》,分享了他在這么多年來一直堅持不懈的對 Go 進行性能測試的記載。
今天基于此分享給大家,有所調整和精簡。
原作者對 Go1.0 到 Go1.22 的所有 Go 版本進行了性能測試,包含了在 Go 1.20 中新增的性能分析引導優化(PGO)的結果。
Go 測試項目用的是 GoAWK[1] 項目,該項目用 Go 寫的 AWK 解釋器,支持 CSV。
GoAWK 使用案例如下:
$ go install github.com/benhoyt/goawk@latest$ goawk 'BEGIN { print "foo", 42 }'foo 42$ echo 1 2 3 | goawk '{ print $1 + $3 }'4# Or use GoAWK's CSV and @"named-field" support:$ echo -e 'name,amount/nBob,17.50/nJill,20/n"Boba Fett",100.00' | / goawk -i csv -H '{ total += @"amount" } END { print total }'137.5
接下來正式開始進行性能測試,主要是同項目多版本測試的模式。
原作者(下稱:我)通過在兩個 AWK 程序上運行 GoAWK 來測試這一點,這兩個程序代表了使用 AWK 可以做的極端不同情況:I/O 操作與字符串處理,以及數值計算。
首先是 countwords,這是一個字符串處理任務,它計算輸入中單詞的頻率并打印出帶有計數的單詞。這是 AWK 腳本的典型應用。
程序輸入是某本書的 10 倍串聯版本的內容。
以下是部分代碼:
{ for (i=1; i<=NF; i++) counts[tolower($i)]++}END { for (k in counts) print k, counts[k]}
第二個程序是 sumloop,這是一個緊湊的循環,它多次將循環計數器加到一個變量上。
這個程序并不是 AWK 的典型用法,但它是測試 GoAWK 字節碼解釋器循環的好方法。
以下是部分代碼:
BEGIN { for (i=0; i<10000000; i++) sum += i+i+i+i+i}
注:我不得不對 GoAWK 的代碼做了一些微調,以便它能在舊版本的 Go 上編譯。特別是對于 Go 1.0,因為它沒有 bufio.Scanner,而 GoAWK 在很大程度上依賴它。我為 1.0 使用了 Go 1.1 的 bufio.Scanner 實現。
圖表中的計時數字是我在 x86-64 Linux 筆記本電腦上運行三次中的最佳時間(以秒為單位)。藍線代表 countwords 程序,紅線代表 sumloop 程序。
Go 各版本的性能測試結果的圖表如下:
請注意,這次 Y 軸是對數的,目的是為了更清晰地看到最近版本中更微妙的改進。
圖表中還包括了每個 Go 版本下的 GoAWK 二進制文件大小 —— 那是淺灰色線。
1、Go 最大的改進出現在 1.3、1.5、1.7 和 1.12 版本中。在此之后的版本,性能提升變得非常漸進 —— 這意味著所有容易實現的優化早已完成。
2、Go 1.2 性能變差了,countwords 出現了一個奇怪的峰值:它從 1.1 的 7.5 秒增加到 1.2 的 25.5 秒(!),然后下降到 1.3 的 2.8 秒。
我通過分析并注意到 Go 運行時棧操作占據了運行時間的很大一部分,找出了 1.2 異常的原因。
以下是 pprof 輸出的前幾行:
$ go tool pprof --text ./goawk_1.2 go12.profTotal: 1830 samples 332 18.1% 18.1% 332 18.1% runtime.newstack 296 16.2% 34.3% 296 16.2% runtime.memclr 281 15.4% 49.7% 281 15.4% runtime.oldstack 222 12.1% 61.8% 619 33.8% github.com/benhoyt/goawk/interp.(*interp).execute 91 5.0% 66.8% 91 5.0% runtime.lessstack 75 4.1% 70.9% 133 7.3% github.com/benhoyt/goawk/interp.(*interp).callBuiltin 57 3.1% 74.0% 57 3.1% runtime.stackfree 53 2.9% 76.9% 81 4.4% strings.FieldsFunc ...
這幾乎可以肯定是由于在 1.3 中修復的堆棧 “熱分裂” 問題[2]所導致的性能下降,因為在該版本 Go 團隊將 goroutine 棧的實現從舊的分段模型改為連續模型。
3、PGO 僅將性能提高了幾個百分點,使用 Go 1.22,countwords 大約提高了 2%,sumloop 提高了 7%。我使用 PGO 編譯了發布的 GoAWK 二進制文件。
4、二進制文件的大小多年來一直保持相對穩定,除了 1.2 版本中的大幅增加。即使啟用了 PGO,二進制文件的大小也只有大約 5% 的增加,所以我認為通常這是值得的。
從測試結果來看,countwords 現在的運行速度比使用 Go 1.0 時快了大約 8 倍,而 sumloop 快了 24 倍。
Go 所編寫的程序變快的原因有許多,包含但不限于 Go 團隊和外部貢獻者改進了編譯器,并優化了運行時、垃圾回收器和標準庫等。
煎魚注:不得不說,感謝 Go 核心團隊多年來的辛勤工作!讓我們躺著升級個版本,就能實現性能的提高。
[1]GoAWK: https://github.com/benhoyt/goawk
[2]堆棧 “熱分裂” 問題: https://docs.google.com/document/u/0/d/1wAaf1rYoM4S4gtnPh0zOlGzWtrZFQ5suE8qr2sD8uWQ/mobilebasic?_immersive_translate_auto_translate=1
本文鏈接:http://www.tebozhan.com/showinfo-26-88398-0.htmlGo1.0 到 1.22 的性能表現,提高了多少倍?
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: React 全新編譯器太好用了!