2

大家好,我是煎鱼。

Go1.20 即将发布,近期很多大佬提到一个关键词 PGO,说是有很大的提高,很猛...让我一愣一愣,不禁思考是什么?

今天就由煎鱼和大家一起学习。

快速了解

PGO 是什么

Profile-guided optimization (PGO),翻译过来是使用配置文件引导的优化。也被称为:

  • profile-directed feedback(PDF)
  • feedback-directed optimization(FDO)

PGO 是计算机编程中的一种编译器优化技术,使用分析来提高程序运行时性能。也就是可以提高 Go 运行时的性能。

该项优化是一个通用技术,不局限于某一门语言。像是:

  • 常用的 Chrome 浏览器,在 64 位版本的 Chrome 中从 53 版开始启用 PGO, 32 位版在 54 版中启用。
  • Microsoft Visual C++ 也同样有所使用。
  • AutoFDO 进行了 PGO 的优化,直接将某数据中心中的 C/C++ 程序的性能提高了 5-15%(不用改业务代码)。

这个优化成绩,一听就很振奋人心。

PGO 怎么优化

《Intel Developer Guide and Reference》 中对 PGO 的优化和流程有一个基本介绍,如下内容,分享给大家。

PGO 通过缩小代码大小、减少分支错误预测和重新组织代码布局以减少指令缓存问题来提高应用程序性能。并向编译器提供有关应用程序中最常执行的区域的信息。通过了解这些领域,编译器能够在优化应用程序时更具选择性和针对性。

PGO 由三个阶段组成。如下图:

  • 检测程序。编译器从您的源代码和编译器的特殊代码创建并链接一个检测程序。
  • 运行检测的可执行文件。每次执行插桩代码时,插桩程序都会生成一个动态信息文件,用于最终编译。
  • 最终编译。当您第二次编译时,动态信息文件将合并到一个摘要文件中。使用此文件中的概要信息摘要,编译器尝试优化程序中最频繁的运行路径去执行。

这就是 PGO 这项优化的基本过程了。

新提案

背景

提案作者(Cherry Mui、Austin Clements、Michael Pratt)建议向 Go GC 工具链增加对配置文件引导优化 (PGO) 的支持,可以使得工具链能根据运行时信息执行特定于应用程序和工作负载的优化

说明了就是想提高性能,不改业务代码。

用什么来做

PGO 需要用户参与来收集配置文件并将其反馈到构建过程中才能优化。这是一个大问题。

最符合这个要求的,就是 pprof。最终敲定Go 团队将基于 runtime/pprof 来得到所需 profile,以此来完成 PGO。因为它符合:采集样本开销低、多系统兼容性强、Go 标准且被广泛使用的基准。

也就是有 runtime/pprof 生成的 profile,就能搞 PGO 了!

支持到什么程度

PGO 第一个版本将会先支持 pprof CPU,直接读取 pprof CPU profile 文件来完成优化。预计将在 Go1.20 发布预览版本

在 Go 工具链上,将在 go build 子命令增加 -pgo=<path>,用于显式指定用于 PGO 构建的 profile 文件位置。

可能会有同学说,还得显式指定,太麻烦了?这 Go 团队也考虑到了...

只需要你将其设置为:-pgo=auto,就会自动去读取主目录下的 profile 文件,非常香!

如果不需要,那就直接 -pgo=off 就能完全关闭 PGO。

Go1.20 实现 PGO 的预览版本,配置默认为 off,成熟后会默认为 auto。

从哪里先动手

Go 团队先会专注于 Go 编译器的开发,毕竟这是万物的开始,后续会在 cmd/go 做一些简单的支持。PGO 第一个动手的方向是:函数内联。这项被认为性价比是最高的。

未来展望上,还会包含:devirtualization(去虚拟化,一种编译器优化策略)、特定泛型函数的模板化、基本块排序和函数布局。

甚至后续会用于改进内存行为,例如:改进逃逸行为和内存分配。

看看这个PGO 的未来展望,这个饼,我感觉画的又大又圆(远)...

超前实践

以下来自 @Frederic Branczyk 在《Exploring Go's Profile-Guided Optimizations》一文中,提前使用 PGO 对 Go 官方已经开发的函数内联进行了提前尝鲜。

步骤如下:

首先拉取已实现的 Go 源码并进行编译和导入。如下代码:

git clone https://go.googlesource.com/go
cd go
git fetch https://go.googlesource.com/go refs/changes/63/429863/3 && git checkout -b change-429863 FETCH_HEAD
cd src
./all.bash
cd ..
export PATH="$(pwd)/bin:$PATH" # or add the path to your bashrc/zshrc

进入到 PGO 的内联测试代码:

cd src/cmd/compile/internal/test/testdata/pgo/inline

做提前准备,生成 pprof cpu profile 文件:

go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof

完成准备动作后。我们进行两次测试:一次不用 PGO,一次用 PGO,来进行对比。

不使用 PGO 的情况:

go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m" 2>&1 | grep "can inline"
./inline_hot.go:15:6: can inline D with cost 7 as: func(uint) int { return int((i + (wSize - 1)) >> lWSize) }
./inline_hot.go:19:6: can inline N with cost 20 as: func(uint) *BS { bs = &BS{...}; return bs }
...

使用 PGO 的情况:

go test -run=none -tags='' -timeout=9m0s -gcflags="-m -m -pgoprofile inline_hot.pprof"

用于如下对比:

go test -o inline_hot.test -bench=. -cpuprofile inline_hot.pprof -count=100 > without_pgo.txt
go test -o inline_hot.test -bench=. -gcflags="-pgoprofile inline_hot.pprof" -count=100 > with_pgo.txt
benchstat without_pgo.txt with_pgo.txt
name  old time/op  new time/op  delta
A-10   960µs ± 2%   950µs ± 1%  -1.05%  (p=0.000 n=98+83)

从结论来看,引入 PGO 后有了 1% 的性能改进。当然,这只是一小段测试代码。不同的程序结果会不一样。

总结

PGO 是一门编译器优化技术,能够在不改业务代码的情况下,给你的应用程序带来一定的性能提升。在 Go PGO 中将会依托 runtime/pprof 所生成的 profile 来完成(需改造),也算是做了一个不错的串联。

另外从需求出发点来看,这项优化感觉更多的来自开发同学的兴趣优化,官方 issues 中并没有指出是由于什么用户痛点导致的要去开发这项功能。

不过后续如果遇到一些需要进一步优化的 Go 程序,PGO 将会是一个不错的选择。毕竟不用改业务代码。

文章持续更新,可以微信搜【脑子进煎鱼了】阅读,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言可以看 Go 学习地图和路线,欢迎 Star 催更。

Go 图书系列

推荐阅读


煎鱼
8.4k 声望12.8k 粉丝