背景
Go 1.20版本于2023年2月份正式公布,在这个版本里引入了PGO性能优化机制。
PGO的英文全称是Profile Guided Optimization,基本原理分为以下2个步骤:
- 先对程序做profiling,收集程序运行时的数据,生成profiling文件。
- 编译程序时启用PGO选项,编译器会依据.pgo文件里的内容对程序做性能优化。
咱们都晓得在编译程序的时候,编译器会对程序做很多优化,包含大家熟知的内联优化(inline optimization)、逃逸剖析(escape analysis)、常数流传(constant propagation)。这些优化是编译器能够间接通过分析程序源代码来实现的。
然而有些优化是无奈通过解析源代码来实现的。
比方一个函数里有很多if/else条件分支判断,咱们可能心愿编译器主动帮咱们优化条件分支程序,来放慢条件分支的判断,晋升程序性能。
然而,编译器可能是无奈晓得哪些条件分支进入的次数多,哪些条件分支进入的次数少,因为这个和程序的输出是有关系的。
这个时候,做编译器优化的人就想到了PGO: Profile Guided Optimization。
PGO的原理很简略,那就是先把程序跑起来,收集程序运行过程中的数据。而后编译器再依据收集到的程序运行时数据来分析程序的行为,进而做针对性的性能优化。
比方程序能够收集到哪些条件分支进入的次数更多,就把该条件分支的判断放在后面,这样能够缩小条件判断的耗时,晋升程序性能。
那Go语言如何应用PGO来优化程序的性能呢?咱们接下来看看具体的例子。
示例
咱们实现一个web接口/render
,该接口以markdown文件的二进制格局作为输出,将markdown格局转换为html格局返回。
咱们借助 gitlab.com/golang-commonmark/markdown
我的项目来实现该接口。
环境搭建
$ go mod init example.com/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.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 mod tidy$ go build -o markdown.nopgo$ ./markdown.nopgo2023/02/25 22:30:51 Serving on port 8080...
程序主目录下新建input.md
文件,内容能够自定义,合乎markdown语法即可。
我演示的例子里用到了input.md 这个markdown文件。
通过curl命令发送markdown文件的二进制内容给/render
接口。
$ curl --data-binary @input.md http://localhost: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>...
能够看到该接口返回了input.md
文件内容对应的html格局。
Profiling
那接下来咱们给main.go
程序做profiling,失去程序运行时的数据,而后通过PGO来做性能优化。
在main.go
里,有import net/http/pprof 这个库,它会在原来已有的web接口/render
的根底上,新增一个新的web接口/debug/pprof/profile
,咱们能够通过申请这个profiling接口来获取程序运行时的数据。
在程序主目录下,新增load子目录,在load子目录下新增
main.go
的文件,load/main.go
运行时会一直申请下面./markdown.nogpo
启动的server的/render
接口,来模拟程序理论运行时的状况。$ go run example.com/markdown/load
申请profiling接口来获取程序运行时数据。
$ curl -o cpu.pprof "http://localhost:8080/debug/pprof/profile?seconds=30"
期待30秒,curl命令会完结,在程序主目录下会生成cpu.pprof
文件。
留神:要应用Go 1.20版本去编译和运行程序。
PGO优化程序
$ mv cpu.pprof default.pgo$ go build -pgo=auto -o markdown.withpgo
go build
编译程序的时候,启用-pgo
选项。
-pgo
既能够反对指定的profiling文件,也能够反对auto
模式。
如果是auto
模式,会主动寻找程序主目录下名为default.pgo
的profiling文件。
Go官网举荐大家应用auto
模式,而且把default.pgo
文件也寄存在程序主目录下保护,这样不便我的项目所有开发者应用default.pgo
来对程序做性能优化。
Go 1.20版本里,-pgo
选项的默认值是off
,咱们必须增加-pgo=auto
来开启PGO优化。
将来的Go版本里,官网打算将-pgo
选项的默认值设置为auto
。
性能比照
在程序的子目录load
下新增bench_test.go
文件,bench_test.go
里应用Go性能测试的Benchmark框架来给server做压力测试。
未开启PGO优化的场景
启用未开启PGO优化的server程序:
$ ./markdown.nopgo
开启压力测试:
$ go test example.com/markdown/load -bench=. -count=20 -source ../input.md > nopgo.txt
开启PGO优化的场景
启用开启了PGO优化的server程序:
$ ./markdown.withpgo
开启压力测试:
$ go test example.com/markdown/load -bench=. -count=20 -source ../input.md > withpgo.txt
综合比照
通过下面压力测试失去的nopgo.txt
和withpgo.txt
来做性能比拟。
$ go install golang.org/x/perf/cmd/benchstat@latest$ benchstat nopgo.txt withpgo.txtgoos: darwingoarch: amd64pkg: example.com/markdown/loadcpu: Intel(R) Core(TM) i5-5250U CPU @ 1.60GHz │ nopgo.txt │ withpgo.txt │ │ sec/op │ sec/op vs base │Load-4 447.3µ ± 7% 401.3µ ± 1% -10.29% (p=0.000 n=20)
能够看到,应用PGO优化后,程序的性能晋升了10.29%,这个晋升成果十分可观。
在Go 1.20版本里,应用PGO之后,通常程序的性能能够晋升2%-4%左右。
后续的版本里,编译器还会持续优化PGO机制,进一步晋升程序的性能。
总结
Go 1.20版本引入了PGO来让编译器对程序做性能优化。PGO应用分2个步骤:
- 先失去一个profiling文件。
- 应用
go build
编译时开启PGO选项,通过profiling文件来领导编译器对程序做性能优化。
在生产环境里,咱们能够收集近段时间的profiling数据,而后通过PGO去优化程序,以晋升零碎解决性能。
更多对于PGO的应用阐明和最佳实际能够参考profile-guided optimization user guide。
源代码地址:pgo optimization source code。
举荐浏览
- Go 1.20来了,看看都有哪些变动
- Go面试题系列,看看你会几题
- Go常见谬误和最佳实际系列
开源地址
文章和示例代码开源在GitHub: Go语言高级、中级和高级教程。
公众号:coding进阶。关注公众号能够获取最新Go面试题和技术栈。
集体网站:Jincheng's Blog。
知乎:无忌。
福利
我为大家整顿了一份后端开发学习材料礼包,蕴含编程语言入门到进阶常识(Go、C++、Python)、后端开发技术栈、面试题等。
关注公众号「coding进阶」,发送音讯 backend 支付材料礼包,这份材料会不定期更新,退出我感觉有价值的材料。还能够发送音讯「进群」,和同行一起交流学习,答疑解惑。
References
- https://go.dev/blog/pgo-preview