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