從 Go 1.0 到 1.22 的效能表現
兩年前,我曾比較了我的 GoAWK 直譯器在 Go 1.2 到 1.18 各個版本上的兩項不同效能測試。
在這篇文章中,我重新執行了這些測試,補上了先前缺少的 Go 版本(1.0 和 1.1),也加入了新版本(1.19 到 1.22)的結果。我也包含了啟用設定檔導引最佳化(PGO)的測試結果,這項功能是在 Go 1.20 加入的。為了讓你不用回頭重讀舊文也能了解測試的設定,我會大量引用原文的內容。
用 Go 撰寫的程式變快的原因有很多:Go 團隊與外部貢獻者持續改進編譯器,並對執行環境、垃圾回收機制和標準函式庫進行了最佳化。在此,我們比較使用從 1.0 到 1.22(撰寫本文時的最新版本)各個已發布的 Go 版本來編譯 GoAWK 時的效能表現。
我是透過在 GoAWK 上執行兩個 AWK 程式來進行測試,這兩個程式代表了 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 bytecode 直譯器迴圈的測試:
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(順帶一提,上次我把結果標示錯了)。請注意,這次 Y 軸採用對數刻度,以便更清楚地呈現近年版本中較細微的改進。
圖表中也包含了各個 Go 版本所編譯出的 GoAWK 執行檔大小——也就是淺灰色的那條線。
和上次一樣,我使用一個Python 腳本來執行所有測試並測量時間。以下是圖表(如果你偏好表格,也可以看表格版):

最顯著的效能提升出現在 1.3、1.5、1.7 和 1.12 版。在此之後,速度的提升就非常緩慢了——所有唾手可得的優化早就已經完成了。
這次 countwords 在 Go 1.2 出現了奇怪的效能暴增:執行時間從 1.1 版的 7.5 秒暴增到 1.2 版的 25.5 秒(!),然後在 1.3 版又降回 2.8 秒。這幾乎可以確定是由堆疊「hot split」問題所造成,該問題在 1.3 版中已被修復,當時 Go 團隊將「goroutine 堆疊的實作從舊有的『分段式』模型改為連續式模型」。
我是透過效能分析找出 1.2 版異常的原因,並注意到執行階段的堆疊操作佔了執行時間的極大比例。以下是 pprof 輸出的前幾行:
$ go tool pprof --text ./goawk_1.2 go12.prof
Total: 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
...啟用 PGO 後效能僅提升了幾個百分點,以 Go 1.22 來說,countwords 約提升 2%,sumloop 約提升 7%。我發布的 GoAWK 執行檔都是以啟用 PGO 的方式編譯的。
執行檔大小這些年來一直保持相當穩定,除了在 1.2 版有一次大幅增加。即使啟用 PGO,執行檔也只會大約增加 5%,所以我認為通常還是值得啟用的。
整體而言,countwords 現在的速度約為使用 Go 1.0 時的 8 倍,而 sumloop 則快了 24 倍。感謝 Go 團隊這些年來的辛勤付出!
隨機一篇部落格
留言
登入後參與討論