Golang 1.20 在 go 编译器引入了对配置文件引导优化 (PGO) 的支持。这允许指导编译器根据系统的真实行为引入优化。在 Cloudflare 的可观察性团队中,我们维护着一些基于 Go 的服务,这些服务在全球范围内使用数千个内核,因此,即使宣传的节省 2-7%,也能大幅减少我们的 CPU 占用空间,而且实际上是免费的。这将减少我们内部服务的 CPU 使用率,释放这些资源来满足客户请求,从而显着改善我们的客户体验。在这篇文章中,我将介绍我们为试验 PGO 而创建的流程 - 在我们的生产基础设施中收集代表性配置文件,然后部署新的 PGO 二进制文件并测量 CPU 节省。
PGO如何运作?
PGO 本身并不是 Go 专用的工具,尽管它相对较新。PGO 允许您从生产运行的程序中获取 CPU 配置文件,并使用它来优化该程序生成的程序集。这包括一系列不同的优化,例如更积极地内联频繁使用的函数、重新设计分支预测以支持更常见的分支,以及重新排列生成的代码以将热路径集中在一起以节省 CPU 缓存交换。
使用 PGO 的一般流程是编译非 PGO 二进制文件并将其部署到生产环境,从生产环境中的二进制文件收集 CPU 配置文件,然后使用该 CPU 配置文件编译第二个二进制文件。CPU 配置文件包含 CPU 在执行程序时花费最多时间的示例,这为编译器在做出优化程序决策时提供了有价值的上下文。例如,编译器可能会选择内联一个被多次调用的函数以减少函数调用开销,或者可能会选择展开一个跳转特别频繁的循环。至关重要的是,使用生产环境中的配置文件可以比任何前期预制方法更有效地指导编译器。
一个实际的例子
在可观察性团队中,我们运营一个称为“wshim”的系统。Wshim 是一项在我们的每台边缘服务器上运行的服务,为来自我们内部 Cloudflare Workers 的遥测提供推送网关。由于该服务在每台服务器上运行,并且每次调用内部工作线程时都会调用该服务,因此 wshim 需要大量 CPU 时间来运行。为了准确跟踪用量,我们将 wshim 放入其自己的 cgroup 中,并使用 cadvisor 公开与其使用的资源相关的 Prometheus 指标。
在部署 PGO 之前,wshim 在全球使用了 3000 多个核:
container_cpu_time_seconds
是我们的内部指标,用于跟踪 CPU 在全球范围内运行 wshim 所花费的时间。即使节省 2%,我们也会为客户返还 60 个核心,从而使 Cloudflare 网络更加高效。
部署 PGO 的第一步是从我们全球的服务器收集代表性配置文件。我们遇到的第一个问题是,我们运行数千台服务器,每台服务器在给定时间点都有不同的使用模式 - 在白天提供大量请求的数据中心与位于本地的不同数据中心具有不同的使用模式。因此,准确选择要分析的服务器对于收集良好的配置文件供 PGO 使用至关重要。
最后,我们决定最好的样本来自那些负载较重的数据中心——这些数据中心的 wshim 最慢部分最为明显。更进一步,我们只会从我们的一级数据中心收集配置文件。这些数据中心为我们人口最稠密的地区提供服务,通常是我们最大的数据中心,并且在高峰时段通常负载非常重。
具体来说,我们可以通过查询 Thanos 基础设施来获取高 CPU 服务器列表:
num_profiles="1000"
cloudflared access curl "https://thanos/api/v1/query?query=topk%28${num_profiles}%2Cinstance%3Acontainer_cpu_time_seconds_total%3Arate2m%7Bapp_name%3D%22wshim.service%22%7D%29&dedup=true&partial_response=true" --compressed | jq '.data.result[].metric.instance' -r > "${instances_file}"
Go 使用 pprof 使实际获取 CPU 配置文件变得异常简单。为了让我们的工程师在生产中调试他们的系统,我们提供了一种轻松检索生产配置文件的方法,我们可以在此处使用。Wshim 提供了一个 pprof 接口,我们可以使用它来检索配置文件,并且我们可以使用 bash 再次收集这些配置文件:
while read instance; do fetch-pprof $instance –port 8976 –seconds 30' > "${working_dir}/${instance}.pprof" & done < "${instances_file}"
wait $(jobs -p)
然后使用 go tool 将所有收集到的配置文件合并为一个:
go tool pprof -proto "${working_dir}/"*.pprof > profile.pprof
我们将使用这个合并的配置文件来编译 pprof 二进制文件。因此,我们将其提交到我们的存储库中,以便它与 wshim 的所有其他部署组件一起存在:
~/cf-repos/wshim ± master
23/01/2024 10:49:08 AEDT❯ tree pgo
pgo
├── README.md
├── fetch-profiles.sh
└── profile.pprof
并更新我们的 Makefile 以将
-pgo
标志传递给
go build
命令:
build:
go build -pgo ./pgo/profile.pprof -o /tmp/wshim ./cmd/wshim
之后,我们可以像任何其他版本一样构建和部署新的 PGO 优化版本的 wshim。
结果
部署新版本后,我们可以检查 CPU 指标,看看是否有任何有意义的节省。众所周知,资源使用情况很难比较。由于 wshim 的 CPU 使用率随着任何给定服务器接收的流量而变化,因此它具有许多潜在的混淆变量,包括一天中的不同时间段、一年中的某一天以及是否存在影响数据中心的任何主动攻击。话虽这么说,我们可以采取一些数字来很好地表明任何潜在的节省。
首先,我们可以查看部署前后wshim的CPU使用情况。这可能会受到两组之间的时间差异的影响,但它显示出相当大的改进。由于我们的版本只需不到两个小时即可滚动到每个 1 级数据中心,因此我们可以使用 PromQL 的“offset”运算符来衡量差异: