原文在這里。
【資料圖】
原文發布于2023年2月8日
在構建Go二進制文件時,Go編譯器會進行優化,以盡可能生成性能最佳的二進制文件。例如,常量傳播可以在編譯時對常量表達式進行求值,避免了運行時的計算開銷;逃逸分析可以避免對局部作用域對象進行堆分配,從而減少了垃圾回收的負擔;內聯則將簡單函數的代碼體復制到調用處,通常能夠進一步優化調用處的代碼(例如額外的常量傳播或更好的逃逸分析)。
Go在發布的每個版本中都會改進優化,但這并不總是一項容易的任務。某些優化是可調節的,但編譯器不能對每個函數都進行過度激進的優化,因為過于激進的優化實際上可能會損害性能或導致過長的構建時間。其他優化要求編譯器對函數中的“常見”和“不常見”路徑進行判斷。編譯器必須根據靜態啟發式規則進行最佳猜測,因為它無法在運行時知道哪些情況將是常見的。
但現在編譯器可以在運行時知道哪些情況是常見的了。
在沒有關于代碼在生產環境中如何使用的確切信息的情況下,編譯器只能對包的源代碼進行操作。但是我們確實有一種工具來評估生產行為:性能分析。如果我們向編譯器提供一個性能分析文件,它就可以做出更明智的決策:對最常用的函數進行更積極的優化,或更準確地選擇常見情況。
使用應用程序行為的性能分析文件進行編譯器優化的方法被稱為基于性能分析的優化(Profile-Guided Optimization,簡稱PGO,也被稱為反饋導向優化(Feedback-Directed Optimization,簡稱FDO))。
PGO/FDO通過收集和分析運行時的性能數據,使得編譯器能夠更準確地了解代碼的執行特性,從而進行更精細的優化。通過結合靜態分析和動態運行時數據,PGO/FDO可以產生更優化的代碼,提高程序的性能和效率。這種技術在提高大型復雜應用程序的性能方面非常有用,特別是對于高度頻繁執行的代碼路徑進行優化。
Go 1.20中包含了PGO的初步支持,作為預覽版本提供。請參閱profile-guided optimization user guide以獲取完整的文檔。盡管距離在生產環境中使用還有一段距離,但仍希望大家在工作中使用,并反饋遇到的問題或意見。
以Markdown轉HTML服務為例:用戶通過/render上傳Markdown文件,然后接收轉換后的HTML文件。這里使用gitlab.com/golang-commonmark/markdown。
$ go mod init example.com/markdown $ go get gitlab.com/golang-commonmark/markdown main.go內容:
package mainimport ("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.Bufferif 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$ ./markdown.nopgo2023/06/25 11:27:13 Serving on port 8080... 使用Go項目的README來進行測試:
$ curl -o README.md -L "https://raw.githubusercontent.com/golang/go/c16c2c49e2fa98ae551fc6335215fadd62d33542/README.md" $ curl --data-binary @README.md http://localhost:8080/render The Go Programming Language
Go is an open source programming language that makes it easy to build simple,reliable, and efficient software.
... Note that the Go project uses the issue tracker for bug reports andproposals only. See https://go.dev/wiki/Questions for a list ofplaces to ask questions about the Go language.
現在我們來采集一個profile文件,再使用PGO來重新構建服務,看看性能能提升多少。
在main.go中,我們導入了net/http/pprof包,它會自動為服務器添加一個/debug/pprof/profile地址,用于獲取CPU分析數據。
通常情況下,我們都是從生產環境中收集性能分析數據,以便編譯器能夠獲取在實際生產環境中的行為情況。但這個示例沒有一個真實的“生產”環境,我們將創建一個簡單的程序來生成負載,同時收集性能分析數據。將該程序的源碼復制到load/main.go,并啟動負載生成器(確保服務器仍在運行!)。
$ go run example.com/markdown/load下載性能分析文件:
$ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30" 下載完成后,關閉服務。
我們可以使用go build命令的-pgo標志要求Go工具鏈使用PGO進行構建。-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我們將使用一個基于Go的基準測試版本的負載生成器來評估PGO對性能的影響。將這個基準測試的代碼復制到load/bench_test.go文件中。
首先沒有使用PGO的情況下進行測試:
$ ./markdown.nopgo 進行測試:
$ go test example.com/markdown/load -bench=. -count=100 -source ../README.md > nopgo.txt然后啟用PGO:
$ ./markdown.withpgo 進行測試:
$ go test example.com/markdown/load -bench=. -count=100 -source ../README.md > withpgo.txt運行結束后進行結果對比:
$ go install golang.org/x/perf/cmd/benchstat@latest $ benchstat nopgo.txt withpgo.txtgoos: linuxgoarch: amd64pkg: example.com/markdown/loadcpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz │ nopgo.txt │ withpgo.txt │ │ sec/op │ sec/op vs base │Load-8 445.1μ ± 4% 408.6μ ± 2% -8.21% (p=0.000 n=100)新版本大約快了8.2%!在Go 1.20中,通過啟用PGO,可以獲得2%到4%的CPU使用率提升。性能分析文件包含了關于應用程序行為的豐富信息,而Go 1.20僅僅開始利用這些信息進行內聯優化。未來的發布版本將繼續改進性能,因為編譯器的更多部分將利用PGO帶來的好處。
原文中效率提升了2.6%
文中的代碼可以在這里找到。
聲明:本作品采用署名-非商業性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請注明出處。Author: mengbinblog: mengbinGithub: mengbin92cnblogs: 戀水無意
標簽: