GO PGO优化
礼物说PGO在Go 1.21中已经可以在生产中使用了。据官方原文描述可以提升2%-7%的CPU效率。收益还是相当可观的,那我们就基于目前的项目做一个简单的尝试,看下最终的结果如何。
什么是PGO
PGO
可以翻译为基于profile指导的优化
,即根据生产环境的CPU使用情况(pprof文件)来优化编译效率,试图生成性能最佳的二进制文件。除了一些常见的优化方式,例如
- 常量传播(constant propagation)可以在编译时计算常量表达式的值,避免了运行时的计算开销。
- 逃逸分析(Escape analysis)可以避免为局部作用域的对象分配堆内存,从而避免GC的开销。
- 内联(Inlining)会将简单函数的函数体拷贝到调用者中,这通常可以在调用者中启用进一步的优化(例如额外的常量传播或更好的逃逸分析)。
- 去虚拟化(Devirtualization)会将接口值上的间接调用(如果可以静态确定其类型)转换为对具体方法的直接调用(这通常可以内联该调用)。
大家也可以看下这个issue,PGO可以优化的一些列表。
测试PGO
尝试使用一个线上高频接口首页信息流
去测试功能,该接口包括了足够多了io操作以及cpu操作。
首先需要获取一份profile文件我们启动了项目后抓了60s的profile,
wget -O t4.out http://127.0.0.1:8080/debug/pprof/profile\?seconds\=60
并在期间使用postman脚本请求了240次获取的火焰图如下所示:把profile文件放在项目根目录,根据
-pgo
编译出来两个二进制文件go test -test.bench BenchmarkH -test.run BenchmarkH -count=10 -o pgomain > pgomain.txt
go test -test.bench BenchmarkH -test.run BenchmarkH -count=10 -o main > main.txt
通过benchstat发现样本差距太大。。猜测io操作对benchmark影响过大,换种cpu密集型的项目再测试!
使用官方的例子
github.com/prattmic/markdown-pgo/load
来测试go test github.com/prattmic/markdown-pgo/load -bench=. -count=40 -source $(pwd)/home.md > pgo.txt
go test github.com/prattmic/markdown-pgo/load -bench=. -count=40 -source $(pwd)/home.md > nopgo.txt
benchstat nopgo.txt pgo.txt
goos: darwin
goarch: arm64
pkg: github.com/prattmic/markdown-pgo/load
│ nopgo.txt │ pgo2.txt │
│ sec/op │ sec/op vs base │
Load-8 216.1µ ± 0% 215.4µ ± 0% ~ (p=0.143 n=40)
效果并不明显,我们再测试下处理300000次请求,需要使用的cpu使用时间:
➜ pgo go tool pprof -top /tmp/cpu.pprof | grep "Total samples"
Duration: 89.50s, Total samples = 91.85s (102.62%)
➜ pgo go tool pprof -top /tmp/cpu-nopgo.pprof | grep "Total samples"
Duration: 93.66s, Total samples = 92.90s (99.18%)
大约节省1%的CPU时间
结论
整体测试下来有一定的优化程度,但是并没有达到官方的数值2%-7%,在io密集型项目中会更不明显。我们还是更加期待Go会持续的优化PGO,让这一特性能发光发亮。