PEP 774 – 移除 JIT 构建对 LLVM 的要求
- 作者:
- Savannah Ostrowski <savannah at python.org>
- 讨论至:
- Discourse 帖子
- 状态:
- 推迟
- 类型:
- 标准跟踪
- 创建日期:
- 2025年1月27日
- Python 版本:
- 3.14
- 发布历史:
- 2025年1月27日
- 决议:
- 2025年3月14日
摘要
自 Python 3.13 以来,CPython 已经能够通过 Linux 和 Mac 上的 --enable-experimental-jit
标志以及 Windows 上的 --experimental-jit
标志配置和构建实验性的即时 (JIT) 编译器。要启用 JIT 构建 CPython,用户需要在其机器上安装 LLVM(最初使用 LLVM 16,但最近使用 LLVM 19)。LLVM 负责生成对于我们的复制-修补 JIT 至关重要的模板(请参阅 PEP 744)。这些模板是预定义的、特定于架构的模板,用于在运行时生成机器代码。
本 PEP 提议通过将生成的模板托管在 CPython 仓库中,移除启用 JIT 的构建对 LLVM 的构建时依赖。这种方法使我们能够在构建时利用已签入的受支持平台的模板,从而简化贡献者体验并解决 2024 年 9 月 Python 核心开发者冲刺中提出的问题。话虽如此,这里有一个明显的权衡需要考虑,因为改进的开发者体验确实会以增加仓库大小为代价。
重要的是,本 PEP 并非提案接受或拒绝 JIT 本身,而是确定 JIT 构建的 LLVM 构建时依赖在未来是否可接受。如果本 PEP 被拒绝,我们将维持现状,保留 LLVM 构建时要求。虽然此依赖项迄今为止有效地服务于 JIT 开发过程,但它引入了设置复杂性和其他挑战,本 PEP 旨在缓解这些问题。
动机
在 2024 年 9 月举行的 Python 核心开发者冲刺中,讨论了 JIT 的后续步骤——相关讨论也发生在 GitHub 上。作为讨论的一部分,也明确希望移除 JIT 构建对 LLVM 的要求,为在 3.14 中默认发布 JIT 做准备。冲刺中的共识是,为 Tier 1 平台的非调试构建提供预生成模板就足够了,并且将这些文件签入 CPython 仓库足以满足有限数量的平台(尽管已经探索了更多选项;请参阅 被拒绝的想法)。
目前,使用 JIT 构建 CPython 需要 LLVM 作为构建时依赖。尽管不暴露给最终用户,但这种依赖是次优的。要求 LLVM 增加了开发人员和希望启用 JIT 构建 CPython 的用户的设置负担。根据操作系统,操作系统随附的 LLVM 版本可能与 JIT 构建所需的版本不同,这给故障排除和解决带来了额外的复杂性。由于目前只有少数核心开发人员贡献和维护 JIT,我们还希望确保尽可能减少在 JIT 相关代码上工作的摩擦。
通过提议的方法,可以预先生成支持架构的预编译模板,存储在中央位置,并在构建过程中自动使用。这种方法确保了可重现的构建,使 JIT 成为 CPython 未来更稳定和可持续的一部分。
基本原理
本 PEP 提议将 JIT 模板直接签入 CPython 仓库,作为消除我们对 LLVM 构建时依赖的最佳途径。
这种方法
- 为希望使用 JIT 构建 CPython 的用户提供最佳的端到端体验
- 降低了希望为 JIT 做出贡献的用户的入门门槛
- 确保构建在不同平台之间保持可重现和一致,而不依赖外部基础设施或下载机制
- 消除网络条件或托管文件与 CPython 仓库状态之间潜在差异引入的可变性,并且
- 使模板遵守我们对所有其他 JIT 相关代码的相同审查流程
然而,这种方法确实会导致整体仓库大小略有增加。比较过去 90 天提交的仓库增长,实际提交与添加模板后的相同提交之间的差异为每个模板文件 0.03 MB。在整体仓库大小的背景下,这是一个小幅增长,同期增长了 2.55 MB。对于六个模板文件,这相当于上限为 0.18 MB。目前所有六个平台的模板文件总大小为 7.2 MB。[1]
未来,随着寄存器分配的改变,这些模板可能会变得更大,每个模板文件中的每条指令将引入 5-6 个变体(大 5-6 倍)。然而,如果我们最终选择这条路线,我们可以对模板文件进行额外的修改,以帮助抵消这种大小的增加(例如,删除注释,最小化模板)。
规范
本规范概述了提议的更改,以移除对 LLVM 的构建时依赖,以及如果本 PEP 被接受,贡献者的体验。
仓库变更
CPython 仓库现在将在 Tools/jit
中的一个名为 stencils/
的新子目录中托管预编译的 JIT 模板。目前,JIT 已针对六个平台进行测试和构建,因此首先我们将签入六个模板文件。将来,如果需要或相关支持其他平台,我们可能会签入额外的模板文件。
cpython/
Tools/
jit/
stencils/
aarch64-apple-darwin.h
aarch64-unknown-linux-gnu.h
i686-pc-windows-msvc.h
x86_64-apple-darwin.h
x86_64-pc-windows-msvc.h
x86_64-pc-linux-gnu.h
工作流程
工作流程变更可以分为两部分,即使用 JIT 启用构建 CPython 和处理 JIT 的实现。
使用 JIT 构建 CPython
预编译的 JIT 模板文件将存储在 Tools/jit/stencils
目录中,每个文件名都对应于上面概述的目标三元组。在构建时,我们决定是使用已签入的模板还是为用户的平台生成新模板。具体来说,对于安装了 LLVM 的贡献者,Tools/jit/stencils
中的 build.py
脚本将允许他们为其平台重新生成模板。没有 LLVM 的用户可以直接依赖仓库中的预编译模板文件。
JIT 实现(或接触 JIT 文件)工作
在持续集成 (CI) 中,当 JIT 相关文件发生更改时,模板文件将自动验证和更新。当打开一个涉及这些文件的拉取请求时,jit.yml
工作流程(用于构建和测试我们的构建)将照常运行。
然而,作为其中的一部分,我们将引入一个新步骤,该步骤将仓库中当前的模板与 CI 中生成的模板进行比较。如果某个平台的模板文件存在差异,则会生成一个用于更新模板的补丁文件,并且该步骤将失败。每个补丁都上传到 GitHub Actions。CI 在所有平台运行完成后,为了方便起见,这些补丁被聚合到一个补丁文件中。您可以下载此聚合补丁,在本地应用它,然后将更新后的模板重新提交到您的分支。然后,随后的 CI 运行将通过。
参考实现
参考实现 的关键部分包括
.github/workflows/jit.yml
:负责生成模板补丁的 CI 工作流程。Tools/jit/stencils
:存储模板的目录。Tools/jit/_targets
:在构建时编译和解析模板的代码。
忽略模板本身和任何必要的 JIT README 更改,支持可重现模板生成和托管的源代码更改是最小的(大约 150 行更改)。
被拒绝的想法
作为本 PEP 研究和探索的一部分,考虑了几种替代方法。然而,下面的想法要么涉及基础设施成本、维护负担,要么导致更差的整体开发者体验。
使用 Git 子模块
Git 子模块对于托管模板来说是一种糟糕的开发者体验,因为它们会产生另一种不必要的摩擦。例如,对 JIT 的任何更新都需要重新生成模板并将其提交到单独的仓库。这引入了一个复杂的过程:您必须更新子模块仓库中的模板,提交这些更改,然后更新主 CPython 仓库中的子模块引用。这种脱节增加了不必要的复杂性和开销,使贡献者和维护者的过程变得脆弱且容易出错。
使用 Git 子树
当使用子树时,嵌入式仓库成为主仓库的一部分,类似于本 PEP 中提议的内容。然而,子树需要额外的工具和维护步骤,这增加了工作流程不必要的复杂性。
托管在单独的仓库中
虽然将 JIT 模板拆分为单独的仓库可以避免与托管模板相关的存储开销,但它增加了构建过程的复杂性。需要额外的工具来获取模板,并且可能会在工作流程中创建额外且不必要的故障点。这种分离也使得难以确保模板与 CPython 源代码树之间的一致性,因为更新必须在仓库之间进行协调。
托管在云存储中
将模板托管在 S3 存储桶或 GitHub 原始存储等云存储中会引入外部依赖,使离线开发工作流程复杂化。此外,根据提供商的不同,这种类型的托管会产生额外的成本,这是我们希望避免的。
使用 Git LFS
Git 大文件存储 (LFS) 为贡献者增加了工具依赖,使开发工作流程复杂化,特别是对于可能尚未熟悉 Git LFS 的用户。Git LFS 与离线工作流程配合不佳,因为由 LFS 管理的文件在检出特定提交时需要互联网连接才能获取,这甚至对基本的 Git 工作流程也造成了干扰。Git LFS 确实有一些免费配额,但超出该配额会有额外费用,这也是我们不希望看到的。
保持现状,LLVM 作为构建时依赖项
将 LLVM 保留为构建时依赖会维持现有采用和贡献的障碍。最终,此选项未能解决可访问性和简单性的核心挑战,并且未能消除在秋季 Python 核心开发者冲刺中被认为不可取的依赖(也是本 PEP 的推动力),使其成为一个糟糕的长期解决方案。
脚注
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0774.rst