PGO 是啥,咋就讓 Go 更快更猛了?

語言: CN / TW / HK

大家好,我是煎魚。

Go1.20 即將釋出,近期很多大佬提到一個關鍵詞 PGO,說是有很大的提高,很猛...讓我一愣一愣,不禁思考是什麼?

今天就由煎魚和大家一起學習。

快速瞭解

PGO 是什麼

Profile-guided optimization (PGO),翻譯過來是使用配置檔案引導的優化。也被稱為: - profile-directed feedback(PDF) - feedback-directed optimization(FDO)

PGO 是計算機程式設計中的一種編譯器優化技術,使用分析來提高程式執行時效能。也就是可以提高 Go 執行時的效能。

該項優化是一個通用技術,不侷限於某一門語言。像是: - 常用的 Chrome 瀏覽器,在 64 位版本的 Chrome 中從 53 版開始啟用 PGO, 32 位版在 54 版中啟用。 - Microsoft Visual C++ 也同樣有所使用。 - AutoFDO 進行了 PGO 的優化,直接將某資料中心中的 C/C++ 程式的效能提高了 5-15%(不用改業務程式碼)。

這個優化成績,一聽就很振奮人心。

PGO 怎麼優化

《Intel Developer Guide and Reference》 中對 PGO 的優化和流程有一個基本介紹,如下內容,分享給大家。

PGO 通過縮小程式碼大小、減少分支錯誤預測和重新組織程式碼佈局以減少指令快取問題來提高應用程式效能。並向編譯器提供有關應用程式中最常執行的區域的資訊。通過了解這些領域,編譯器能夠在優化應用程式時更具選擇性和針對性。

PGO 由三個階段組成。如下圖:

  • 檢測程式。編譯器從您的原始碼和編譯器的特殊程式碼建立並連結一個檢測程式。
  • 執行檢測的可執行檔案。每次執行插樁程式碼時,插樁程式都會生成一個動態資訊檔案,用於最終編譯。
  • 最終編譯。當您第二次編譯時,動態資訊檔案將合併到一個摘要檔案中。使用此檔案中的概要資訊摘要,編譯器嘗試優化程式中最頻繁的執行路徑去執行。

這就是 PGO 這項優化的基本過程了。

新提案

背景

提案作者(Cherry Mui、Austin Clements、Michael Pratt)建議向 Go GC 工具鏈增加對配置檔案引導優化 (PGO) 的支援,可以使得工具鏈能根據執行時資訊執行特定於應用程式和工作負載的優化

說明了就是想提高效能,不改業務程式碼。

用什麼來做

PGO 需要使用者參與來收集配置檔案並將其反饋到構建過程中才能優化。這是一個大問題。

最符合這個要求的,就是 pprof。最終敲定Go 團隊將基於 runtime/pprof 來得到所需 profile,以此來完成 PGO。因為它符合:採集樣本開銷低、多系統相容性強、Go 標準且被廣泛使用的基準。

也就是有 runtime/pprof 生成的 profile,就能搞 PGO 了!

支援到什麼程度

PGO 第一個版本將會先支援 pprof CPU,直接讀取 pprof CPU profile 檔案來完成優化。預計將在 Go1.20 釋出預覽版本

在 Go 工具鏈上,將在 go build 子命令增加 -pgo=<path>,用於顯式指定用於 PGO 構建的 profile 檔案位置。

可能會有同學說,還得顯式指定,太麻煩了?這 Go 團隊也考慮到了...

只需要你將其設定為:-pgo=auto,就會自動去讀取主目錄下的 profile 檔案,非常香!

如果不需要,那就直接 -pgo=off 就能完全關閉 PGO。

Go1.20 實現 PGO 的預覽版本,配置預設為 off,成熟後會預設為 auto。

從哪裡先動手

Go 團隊先會專注於 Go 編譯器的開發,畢竟這是萬物的開始,後續會在 cmd/go 做一些簡單的支援。PGO 第一個動手的方向是:函式內聯。這項被認為價效比是最高的。

未來展望上,還會包含:devirtualization(去虛擬化,一種編譯器優化策略)、特定泛型函式的模板化、基本塊排序和函式佈局。

甚至後續會用於改進記憶體行為,例如:改進逃逸行為和記憶體分配。

看看這個PGO 的未來展望,這個餅,我感覺畫的又大又圓(遠)...

超前實踐

以下來自 @Frederic Branczyk 在《Exploring Go's Profile-Guided Optimizations》一文中,提前使用 PGO 對 Go 官方已經開發的函式內聯進行了提前嚐鮮。

步驟如下:

首先拉取已實現的 Go 原始碼並進行編譯和匯入。如下程式碼:

go git clone https://go.googlesource.com/go cd go git fetch https://go.googlesource.com/go refs/changes/63/429863/3 && git checkout -b change-429863 FETCH_HEAD cd src ./all.bash cd .. export PATH="$(pwd)/bin:$PATH" # or add the path to your bashrc/zshrc

進入到 PGO 的內聯測試程式碼:

go cd src/cmd/compile/internal/test/testdata/pgo/inline

做提前準備,生成 pprof cpu profile 檔案:

go go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof

完成準備動作後。我們進行兩次測試:一次不用 PGO,一次用 PGO,來進行對比。

不使用 PGO 的情況:

go go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m" 2>&1 | grep "can inline" ./inline_hot.go:15:6: can inline D with cost 7 as: func(uint) int { return int((i + (wSize - 1)) >> lWSize) } ./inline_hot.go:19:6: can inline N with cost 20 as: func(uint) *BS { bs = &BS{...}; return bs } ...

使用 PGO 的情況:

go go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m -pgoprofile inline_hot.pprof"

用於如下對比:

go go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof -count=100 > without_pgo.txt go test -o inline_hot.test -bench=. -gcflags="-pgoprofile inline_hot.pprof" -count=100 > with_pgo.txt benchstat without_pgo.txt with_pgo.txt name old time/op new time/op delta A-10 960µs ± 2% 950µs ± 1% -1.05% (p=0.000 n=98+83)

從結論來看,引入 PGO 後有了 1% 的效能改進。當然,這只是一小段測試程式碼。不同的程式結果會不一樣。

總結

PGO 是一門編譯器優化技術,能夠在不改業務程式碼的情況下,給你的應用程式帶來一定的效能提升。在 Go PGO 中將會依託 runtime/pprof 所生成的 profile 來完成(需改造),也算是做了一個不錯的串聯。

另外從需求出發點來看,這項優化感覺更多的來自開發同學的興趣優化,官方 issues 中並沒有指出是由於什麼使用者痛點導致的要去開發這項功能。

不過後續如果遇到一些需要進一步優化的 Go 程式,PGO 將會是一個不錯的選擇。畢竟不用改業務程式碼。

文章持續更新,可以微信搜【腦子進煎魚了】閱讀,本文 GitHub github.com/eddycjy/blog 已收錄,學習 Go 語言可以看 Go 學習地圖和路線,歡迎 Star 催更。

Go 圖書系列

推薦閱讀