原文在這里。
【資料圖】
原文發(fā)布于2023年2月8日
在構(gòu)建Go二進制文件時,Go編譯器會進行優(yōu)化,以盡可能生成性能最佳的二進制文件。例如,常量傳播可以在編譯時對常量表達式進行求值,避免了運行時的計算開銷;逃逸分析可以避免對局部作用域?qū)ο筮M行堆分配,從而減少了垃圾回收的負(fù)擔(dān);內(nèi)聯(lián)則將簡單函數(shù)的代碼體復(fù)制到調(diào)用處,通常能夠進一步優(yōu)化調(diào)用處的代碼(例如額外的常量傳播或更好的逃逸分析)。
Go在發(fā)布的每個版本中都會改進優(yōu)化,但這并不總是一項容易的任務(wù)。某些優(yōu)化是可調(diào)節(jié)的,但編譯器不能對每個函數(shù)都進行過度激進的優(yōu)化,因為過于激進的優(yōu)化實際上可能會損害性能或?qū)е逻^長的構(gòu)建時間。其他優(yōu)化要求編譯器對函數(shù)中的“常見”和“不常見”路徑進行判斷。編譯器必須根據(jù)靜態(tài)啟發(fā)式規(guī)則進行最佳猜測,因為它無法在運行時知道哪些情況將是常見的。
但現(xiàn)在編譯器可以在運行時知道哪些情況是常見的了。
在沒有關(guān)于代碼在生產(chǎn)環(huán)境中如何使用的確切信息的情況下,編譯器只能對包的源代碼進行操作。但是我們確實有一種工具來評估生產(chǎn)行為:性能分析。如果我們向編譯器提供一個性能分析文件,它就可以做出更明智的決策:對最常用的函數(shù)進行更積極的優(yōu)化,或更準(zhǔn)確地選擇常見情況。
使用應(yīng)用程序行為的性能分析文件進行編譯器優(yōu)化的方法被稱為基于性能分析的優(yōu)化(Profile-Guided Optimization,簡稱PGO,也被稱為反饋導(dǎo)向優(yōu)化(Feedback-Directed Optimization,簡稱FDO))。
PGO/FDO通過收集和分析運行時的性能數(shù)據(jù),使得編譯器能夠更準(zhǔn)確地了解代碼的執(zhí)行特性,從而進行更精細(xì)的優(yōu)化。通過結(jié)合靜態(tài)分析和動態(tài)運行時數(shù)據(jù),PGO/FDO可以產(chǎn)生更優(yōu)化的代碼,提高程序的性能和效率。這種技術(shù)在提高大型復(fù)雜應(yīng)用程序的性能方面非常有用,特別是對于高度頻繁執(zhí)行的代碼路徑進行優(yōu)化。
Go 1.20中包含了PGO的初步支持,作為預(yù)覽版本提供。請參閱profile-guided optimization user guide以獲取完整的文檔。盡管距離在生產(chǎn)環(huán)境中使用還有一段距離,但仍希望大家在工作中使用,并反饋遇到的問題或意見。
以Markdown轉(zhuǎn)HTML服務(wù)為例:用戶通過/render
上傳Markdown文件,然后接收轉(zhuǎn)換后的HTML文件。這里使用gitlab.com/golang-commonmark/markdown。
$ go mod init example.com/markdown $ go get gitlab.com/golang-commonmark/markdown
main.go
內(nèi)容:
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))}
啟動服務(wù):
$ 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.
現(xiàn)在我們來采集一個profile文件,再使用PGO來重新構(gòu)建服務(wù),看看性能能提升多少。
在main.go
中,我們導(dǎo)入了net/http/pprof
包,它會自動為服務(wù)器添加一個/debug/pprof/profile
地址,用于獲取CPU分析數(shù)據(jù)。
通常情況下,我們都是從生產(chǎn)環(huán)境中收集性能分析數(shù)據(jù),以便編譯器能夠獲取在實際生產(chǎn)環(huán)境中的行為情況。但這個示例沒有一個真實的“生產(chǎn)”環(huán)境,我們將創(chuàng)建一個簡單的程序來生成負(fù)載,同時收集性能分析數(shù)據(jù)。將該程序的源碼復(fù)制到load/main.go
,并啟動負(fù)載生成器(確保服務(wù)器仍在運行?。?。
$ go run example.com/markdown/load
下載性能分析文件:
$ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30"
下載完成后,關(guān)閉服務(wù)。
我們可以使用go build
命令的-pgo
標(biāo)志要求Go工具鏈?zhǔn)褂肞GO進行構(gòu)建。-pgo
標(biāo)志可以接受以下兩種參數(shù):
我們建議將default.pgo
性能分析文件提交到你的代碼倉庫中。將性能分析文件與源代碼放在一起,可以確保用戶只需獲取代碼倉庫(無論是通過版本控制系統(tǒng)還是通過go get
命令),就能自動獲得性能分析文件,并且構(gòu)建過程仍然可重現(xiàn)。在Go 1.20中,默認(rèn)的-pgo
選項是off
,因此用戶仍需要添加-pgo=auto
選項,但預(yù)計將來的Go版本將把默認(rèn)值改為-pgo=auto
,這樣任何構(gòu)建該二進制文件的人都將獲得PGO的好處。
構(gòu)建:
$ mv cpu.pprof default.pgo$ go build -pgo=auto -o markdown.withpgo
我們將使用一個基于Go的基準(zhǔn)測試版本的負(fù)載生成器來評估PGO對性能的影響。將這個基準(zhǔn)測試的代碼復(fù)制到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
運行結(jié)束后進行結(jié)果對比:
$ 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使用率提升。性能分析文件包含了關(guān)于應(yīng)用程序行為的豐富信息,而Go 1.20
僅僅開始利用這些信息進行內(nèi)聯(lián)優(yōu)化。未來的發(fā)布版本將繼續(xù)改進性能,因為編譯器的更多部分將利用PGO帶來的好處。
原文中效率提升了2.6%
文中的代碼可以在這里找到。
聲明:本作品采用署名-非商業(yè)性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請注明出處。Author: mengbinblog: mengbinGithub: mengbin92cnblogs: 戀水無意
標(biāo)簽: