| RFC-0247:在 Fuchsia 中启用 LTO | |
|---|---|
| 状态 | 已接受 |
| 区域 |
|
| 说明 | 在 Fuchsia 中,仅针对由 Clang 工具链生成的目标二进制文件(非调试 [又称发布] build 中的内核除外)启用名为“链接时优化 (LTO)”的编译器功能。 |
| 问题 | |
| Gerrit 更改 | |
| 作者 | |
| 审核人 | |
| 提交日期(年-月-日) | 2024-03-29 |
| 审核日期(年-月-日) | 2024-05-02 |
摘要
我们建议在 Fuchsia 中仅针对由 Clang 工具链生成的目标二进制文件(非调试 [又称发布] build 中的内核除外)启用名为链接时优化 (LTO) 的编译器功能。在此 RFC 中,我们不建议在内核和 Rust 中启用 LTO。
动机
LTO 可以提高运行时性能并缩减代码大小,但会增加 build 时间。LTO 是一项过渡技术,与以下技术结合使用时,可实现性能更高、更安全的系统:
将 LTO 与 PGO 结合使用通常可以获得更好的运行时性能,因为 LTO 可以根据收集的配置文件做出有指导意义的决策。此外,LTO 还可用于一种称为 CFI 的安全缓解技术。在 Fuchsia 上启用 LTO 和 CFI 有助于实现与其他操作系统供应商(例如 Android 和 ChromeOS)的功能对等。
利益相关方
辅导员:
hjfreyer@google.com
审核者:
- aaronwood@google.com
- olivernewman@google.com
- phosek@google.com
已咨询:
- awolter@google.com
- davidroth@google.com
- fmeawad@google.com
- mseaborn@google.com
共同化:
我们已与 EngProd、Performance、Release、Software Assembly、Toolchain 和 Zircon Kernel 团队就此 RFC 的一个版本进行了沟通。
设计
我们提交了一个 CL,以准备在非调试(即发布)build 中默认启用 LTO。我们只需在后续 CL 中将其开启即可。开发者可以使用 GN 实参完全停用 LTO,以缓解构建时间影响。
实现
我们在此 RFC 中实现了提案,并落实了必要的更改。
性能
LTO 具有以下大小和性能优势:
大小优势:我们在 CQ 中运行大小检查器 build,以衡量 LTO 的大小优势,并发现仅在 Clang 生成的二进制文件中启用 LTO 可分别将智能显示屏和 core_size_limits.arm64 中的总代码大小减少 0.17 MiB 和 0.24 MiB。
性能优势:我们在 CQ 中运行了性能构建器,以衡量 LTO 的性能优势。在智能显示屏的 405 项测试中,LTO 改进了 23 项,其中一些增幅超过 10%。此外,LTO 在平台构建器中改进了 5,226 项测试中的 1,959 项。我们还看到平台构建者在性能方面取得了显著的提升,其中一些提升幅度超过 30%。
向后兼容性
无需担心向后兼容性。
安全注意事项
已通过 https://fxbug.dev/317396428 完成安全审核。
隐私注意事项
已通过 https://fxbug.dev/314790650 完成隐私权审核。
测试
我们计划在默认情况下在 ToT 上启用 LTO 后,依靠自动化测试来标记任何功能或性能问题。
文档
需要将 LTO 添加到 Fuchsia 版本说明中。
缺点、替代方案和未知因素
缺点:链接时间增加
在编译各个模块时,编译器可进行的优化有限,而 LTO 通过在链接时执行优化,将优化范围扩大到整个程序。这样会增加链接时间,但可以通过全程序分析和跨模块优化来提高运行时性能。缺点是,LTO 会将更多 build 成本转移到链接时间,从而导致链接成本更高,并且从并行化和缓存中获得的收益更少。
缺点:构建时间增加
启用 LTO 会增加 CI/CQ 发布 build 和开发者 build 的 build 时间。我们通过应用此 CL(使用和不使用 RBE/Goma)测量了干净 build 在 CQ 中的 build 时间影响,并以 MM:SS 格式报告了以下 ninja 步骤的时间。
| CQ 构建器 | RBE | 无 LTO | LTO | 时间变化 |
|---|---|---|---|---|
| core_size_limits.x64-release | 无 RBE/Goma | 39:00 | 41:00 | +2:00 |
| core_size_limits.x64-release | RBE | 6:00 | 8:18 | +2:18 |
| core.x64-release | 无 RBE/Goma | 66:00 | 66:00 | +0:00 |
| core.x64-release | RBE | 20:15 | 26:00 | +5:45 |
| minimal.x64-release | 无 RBE/Goma | 72:00 | 78:00 | +6:00 |
| minimal.x64-release | RBE | 17:48 | 25:00 | +7:12 |
| workbench_eng.x64-release | 无 RBE/Goma | 41:00 | 43:00 | +2:00 |
| workbench_eng.x64-release | RBE | 6:30 | 9:18 | +2:48 |
在下表中,我们展示了运行 NINJA_STATUS=["%es] " fx build 时,开发者 build 时间对仅构建核心的 x64-release 配置的完全清理 build 的影响。
| RBE | 无 LTO | LTO | 时间变化 |
|---|---|---|---|
| 无 RBE/Goma | 26:48 | 27:15 | +0:27 |
| RBE 冷缓存 | 13:58 | 17:52 | +3:54 |
| RBE 暖缓存 | 13:55 | 15:53 | +1:58 |
我们测量了名为 driver_manager 的单个二进制文件的增量 build 时间开销。我们执行了以下步骤来衡量 LTO 对完整 build 流水线的影响。
- 运行
fx build driver_manager以执行干净 build。 - 删除此源文件中的这一行。
- 执行增量构建,并通过
NINJA_STATUS=["%es] " fx build driver_manager测量所用时间。
我们在下表中显示了 driver_manager 的增量 build 时间开销:
| RBE | 无 LTO | LTO | 时间变化 |
|---|---|---|---|
| 无 RBE/Goma | 0:43 | 1:13 | +0:30 |
| RBE | 1:08 | 1:22 | +0:14 |
未知:发现潜在问题
LTO 可能会暴露源代码中的潜在问题,因为它会跨模块执行优化,这可能会使某些已有的假设失效或暴露隐藏的 bug。
未知:对可调试性的影响
此外,还有人担心 LTO 会对代码生成和调试产生影响。虽然 LTO 一般不应影响可调试性,并且我们尚未发现具体案例,但这一点仍不确定。如果提供此类情况,我们可以调查编译器调试信息和调试工具是否在特定情况下与 LTO 交互不佳。
未来工作:在内核中启用 LTO
我们计划研究在内核中启用 LTO 的好处,并在内核中启用 LTO 之前,尝试将 LTO 与 PGO 等其他优化方式相结合。我们决定将在用户空间中启用 LTO 的优势与在内核中启用 LTO 的优势分开,并使内核不在本 RFC 的范围内。
未来工作:在 Rust 中启用 LTO
我们还计划在 Rust 中启用 LTO。初步结果表明,在 Rust 中启用 LTO 可进一步提升大小和性能。不过,我们决定采用分阶段方法,在不同阶段启用 LTO,以最大限度减少构建时间影响和发布风险。
未来工作:启用 FatLTO 和统一 LTO
我们计划在 Fuchsia 中调查启用 FatLTO 和统一 LTO 的事宜。不同的目标平台可能有不同的 build 时间和性能要求。例如,对某些目标应用 LTO 可能有益,但对其他目标(例如测试)应用 LTO 可能无益。FatLTO 和 Unified LTO 可能会有助于减少构建时间开销和复杂性。不过,它们尚未准备好进行全面发布,还需要进行一些调试和测试。
现有技术和参考资料
自 Android 9 以来,Android 一直在内核和其他组件中启用 LTO 和 CFI。Chrome 已部署 LTO、PGO 和 CFI。
我们目前在 Fuchsia 中广泛使用 LTO,具体来说,我们在 Fuchsia 开发中使用的大多数主机工具(例如 ffx)默认情况下都是使用 LTO 构建的,并且 LTO 对于提升这些工具的性能至关重要。
FEC 决定
Fuchsia 工程委员会 (FEC) 已投票决定接受此 RFC,但有一些注意事项。
首先,FEC 同意,对于我们提供给合作伙伴(然后提供给最终用户)的二进制文件,LTO 是朝着正确方向迈出的一步,原因如下:gulfem@google.com 和 phosek@google.com 在 RFC 文本和评论串中提出的所有原因。
其次,FEC 承认此变更的影响尚不确定。 为发布 build 启用 LTO 会对相关方产生正面和负面影响:最终用户将受益于性能的提升;平台开发者会发现优化后的代码更难调试;开发者和产品负责人将受益于更小的二进制文件大小,尤其是在存储空间有限的设备上;平台开发者会发现构建时间更长;基础架构会在编译上花费更多 CPU 周期,但在测试执行上花费更少;等等。没有人能预测到全部影响,即使有人能预测到,合理的利益相关方也可能会对总体影响是正面还是负面产生异议。鉴于这种不确定性,我们认为 Toolchain 团队已尽到应有的谨慎义务。
更改生效后,如果任何利益相关者认为自己受到了过度的负面影响,我们建议他们向工具链团队提出疑虑,如果疑虑未得到解决,则可向 FEC 升级。随着新证据的出现,我们随时可以重新评估此决定。
最后,我们想特别提及一个受到负面影响的利益相关者群体:在正常开发工作流程中以发布模式构建的内核外部 C++ 平台开发者。此变更很可能会导致增量构建时间明显变慢。虽然这令人遗憾,但我们认为启用 LTO 仍然是正确的选择。
不过,这确实指出了一个我们也有的更大担忧:随着我们在更多代码库中推出更智能的优化器,受影响的开发者群体将会扩大,减速程度也会增加。开发者不太可能确切知道其 build 变慢的原因,甚至可能不会有意识地注意到变慢,但其工作效率仍会受到损害。
为避免这种情况,我们呼吁暂停进行会增加 build 时间的其他工具链相关更改,直到我们可以将这些更改应用于我们交付的制品,而不会对团队中的大量开发者造成影响(考虑到团队中有大量开发者无法使用调试 build)。例如,我们可以将当前的两种主要编译模式(调试和发布)改为三种(可能与 Bazel 的“debug、opt 和 fastbuild”编译模式类似),从而解除进一步更改的限制。高成本优化仅在其中一种模式下运行,而其他两种模式则涵盖了绝大多数开发者使用情形。举个反例,记录一组特定的低级编译器标志(供个别开发者选择采用)是不够的,因为这些个别的标志组难以发现、缺乏构建器覆盖率,并且缓存命中率很低。