Go 部落格
配置檔案引導最佳化預覽
當你構建一個 Go 二進位制檔案時,Go 編譯器會執行最佳化,試圖生成效能最佳的二進位制檔案。例如,常量傳播可以在編譯時評估常量表達式,避免執行時評估開銷。逃逸分析可以避免區域性作用域物件的堆分配,從而減少 GC 開銷。函式內聯會將簡單函式的主體複製到呼叫方,這通常能進一步最佳化呼叫方(例如額外的常量傳播或更好的逃逸分析)。
Go 在版本迭代中不斷改進最佳化,但這並非易事。一些最佳化是可調整的,但編譯器不能簡單地對每個函式都“開足馬力”,因為過度激進的最佳化實際上會損害效能或導致過長的構建時間。其他最佳化則要求編譯器判斷函式中的“常見”和“非常見”路徑。由於無法知道哪些情況在執行時會更常見,編譯器必須基於靜態啟發式方法做出最佳猜測。
或者它可以嗎?
如果沒有關於程式碼在生產環境中如何使用的明確資訊,編譯器只能對包的原始碼進行操作。但是我們有一個工具可以評估生產行為:效能剖析。如果我們向編譯器提供一份效能剖析資料,它可以做出更明智的決策:更積極地最佳化最常用的函式,或更準確地選擇常見情況。
將應用程式行為的效能剖析資料用於編譯器最佳化稱為 配置檔案引導最佳化 (PGO)(也稱為反饋導向最佳化 (FDO))。
Go 1.20 包含了對 PGO 的初步支援作為預覽版。有關完整的文件,請參閱配置檔案引導最佳化使用者指南。目前仍存在一些可能阻礙生產環境使用的問題,但我們非常希望您能試用並向我們傳送您遇到的任何反饋或問題。
示例
讓我們構建一個將 Markdown 轉換為 HTML 的服務:使用者將 Markdown 源上傳到 /render
,它返回轉換後的 HTML。我們可以使用 gitlab.com/golang-commonmark/markdown
輕鬆實現這一點。
設定
$ go mod init example.com/markdown
$ go get gitlab.com/golang-commonmark/markdown@bf3e522c626a
在 main.go
中
package main
import (
"bytes"
"io"
"log"
"net/http"
_ "net/http/pprof"
"gitlab.com/golang-commonmark/markdown"
)
func render(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
return
}
src, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("error reading body: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
md := markdown.New(
markdown.XHTMLOutput(true),
markdown.Typographer(true),
markdown.Linkify(true),
markdown.Tables(true),
)
var buf bytes.Buffer
if err := md.Render(&buf, src); err != nil {
log.Printf("error converting markdown: %v", err)
http.Error(w, "Malformed markdown", http.StatusBadRequest)
return
}
if _, err := io.Copy(w, &buf); err != nil {
log.Printf("error writing response: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/render", render)
log.Printf("Serving on port 8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
構建並執行伺服器
$ go build -o markdown.nopgo.exe
$ ./markdown.nopgo.exe
2023/01/19 14:26:24 Serving on port 8080...
讓我們嘗試從另一個終端傳送一些 Markdown。我們可以使用 Go 專案的 README 作為示例文件。
$ curl -o README.md -L "https://raw.githubusercontent.com/golang/go/c16c2c49e2fa98ae551fc6335215fadd62d33542/README.md"
$ curl --data-binary @README.md https://:8080/render
<h1>The Go Programming Language</h1>
<p>Go is an open source programming language that makes it easy to build simple,
reliable, and efficient software.</p>
...
效能剖析
現在我們有了一個可工作的服務,讓我們收集一份效能剖析資料,並使用 PGO 重建,看看是否能獲得更好的效能。
在 main.go
中,我們匯入了 net/http/pprof,它自動為伺服器添加了一個 /debug/pprof/profile
端點,用於獲取 CPU 效能剖析資料。
通常,您會希望從生產環境中收集效能剖析資料,以便編譯器能夠獲得生產環境行為的代表性檢視。由於本例沒有“生產”環境,我們將建立一個簡單的程式來生成負載,同時收集效能剖析資料。將此程式的原始碼複製到 load/main.go
並啟動負載生成器(確保伺服器仍在執行!)。
$ go run example.com/markdown/load
在它執行時,從伺服器下載效能剖析資料
$ curl -o cpu.pprof "https://:8080/debug/pprof/profile?seconds=30"
一旦完成,殺死負載生成器和伺服器。
使用效能剖析資料
我們可以使用 go build
的 -pgo
標誌要求 Go 工具鏈使用 PGO 進行構建。-pgo
接受要使用的效能剖析檔案的路徑,或 auto
,這將使用主包目錄中的 default.pgo
檔案。
我們建議將 default.pgo
配置檔案提交到您的倉庫。將配置檔案與原始碼一起儲存可以確保使用者只需透過拉取倉庫(無論是透過版本控制系統還是透過 go get
)即可自動訪問配置檔案,並且構建仍然可重現。在 Go 1.20 中,預設設定為 -pgo=off
,因此使用者仍需要新增 -pgo=auto
,但未來的 Go 版本預計會將預設值更改為 -pgo=auto
,自動讓構建二進位制檔案的任何人都能受益於 PGO。
讓我們構建
$ mv cpu.pprof default.pgo
$ go build -pgo=auto -o markdown.withpgo.exe
評估
我們將使用負載生成器的 Go 基準測試版本來評估 PGO 對效能的影響。將此基準測試複製到 load/bench_test.go
。
首先,我們將在不使用 PGO 的情況下對伺服器進行基準測試。啟動該伺服器
$ ./markdown.nopgo.exe
在它執行時,執行多次基準測試迭代
$ go test example.com/markdown/load -bench=. -count=20 -source ../README.md > nopgo.txt
一旦完成,殺死原始伺服器並啟動使用 PGO 的版本
$ ./markdown.withpgo.exe
在它執行時,執行多次基準測試迭代
$ go test example.com/markdown/load -bench=. -count=20 -source ../README.md > withpgo.txt
一旦完成,讓我們比較結果
$ go install golang.org/x/perf/cmd/benchstat@latest
$ benchstat nopgo.txt withpgo.txt
goos: linux
goarch: amd64
pkg: example.com/markdown/load
cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz
│ nopgo.txt │ withpgo.txt │
│ sec/op │ sec/op vs base │
Load-12 393.8µ ± 1% 383.6µ ± 1% -2.59% (p=0.000 n=20)
新版本快了大約 2.6%!在 Go 1.20 中,工作負載啟用 PGO 後通常能獲得 2% 到 4% 的 CPU 使用率改進。配置檔案包含有關應用程式行為的大量資訊,而 Go 1.20 僅僅透過使用這些資訊進行內聯來初探其潛力。未來的版本將繼續改進效能,因為編譯器的更多部分將利用 PGO。
後續步驟
在這個例子中,收集效能剖析資料後,我們使用與原始構建中完全相同的原始碼重建了伺服器。在實際場景中,總有持續的開發。因此,我們可能會從正在執行上週程式碼的生產環境中收集效能剖析資料,並用它來構建今天的原始碼。這是完全沒問題的!Go 中的 PGO 可以輕鬆處理原始碼的微小更改。
有關使用 PGO 的更多資訊、最佳實踐以及需要注意的事項,請參閱配置檔案引導最佳化使用者指南。
請向我們傳送您的反饋!PGO 仍處於預覽階段,我們很樂意聽取任何關於使用困難、工作不正確等方面的問題。請在 go.dev/issue/new 提交問題。
致謝
將配置檔案引導最佳化新增到 Go 是一項團隊努力,我特別要感謝 Uber 的 Raj Barik 和 Jin Lin,以及 Google 的 Cherry Mui 和 Austin Clements 做出的貢獻。這種跨社群協作是使 Go 變得偉大的關鍵部分。
下一篇文章:所有可比較型別
上一篇文章:Go 1.20 釋出了!
部落格索引