PEP 744 – JIT 编译

猫勺猫勺 04-20 167 阅读 0 评论

抽象

今年早些时候,一个实验性的“即时”编译器被合并到CPython的开发分支中。虽然最近的 CPython 版本包括其他 内部发生了重大变化,这一增加代表了一个特别的 与 CPython 传统上执行 Python 的方式大相径庭 法典。因此,它值得更广泛的讨论。mAIn

PEP 旨在总结此添加背后的设计决策, 实施的现状,以及使 JIT 成为 CPython 的永久性、非实验性部分。它不寻求提供 全面概述 JIT 的工作原理,而不是专注于 所选方法的特殊优点和缺点,以及 回答了自 JIT 以来提出的许多问题 介绍。

欢迎有兴趣了解更多有关新 JIT 的读者咨询 以下资源

  • 首次介绍的演示文稿 JIT 在 2023 年 CPython Core 开发者冲刺中。它包括相关的 背景,对“复制和修补”技术的简单技术介绍 使用,并在核心开发人员之间公开讨论其设计 目前。

  • 最初的开放获取论文 描述复制和修补。

  • 这篇博文由 论文的作者详细介绍了复制和修补JIT编译器的实现 对于Lua。虽然这是对该方法的一个很好的低级解释,但请注意 它还结合了其他技术并做出实施决策 与 CPython 的 JIT 不是特别相关。

  • 实现本身。

赋予动机

在此之前,CPython 始终通过将 Python 代码编译为 字节码,在运行时解释。这个字节码或多或少是一个 源代码的直接翻译:它是未输入的,并且在很大程度上未经优化

自 Python 3.11 发布以来,CPython 使用了“专门的自适应 interpreter“(PEP 659),它会在运行时使用类型专用版本就地重写这些字节码指令。 这个新的解释器提供了显著的性能改进,尽管 事实上,它的优化潜力受到个体边界的限制 字节码说明。它还收集了大量新的分析信息: 流经程序的类型、特定对象的内存布局,以及 程序中执行最多的路径。换句话说,要优化什么,以及如何优化它。

自 Python 3.12 版本以来,CPython 从类 C 领域特定语言 (DSL) 生成了此解释器。在 除了驯服新的自适应解释器的一些复杂性外, DSL 还允许 CPython 的维护人员避免手写繁琐的样板 解释器、编译器和标准库的许多部分中的代码必须 与指令定义保持同步。这种能力产生大 来自单一事实来源的运行时基础设施数量不仅 便于维护;它还解锁了许多扩展的可能性 CPython以新的方式执行。例如,它使 自动生成用于将指令序列转换为指令序列的表。 等效的较小“微操作”序列,生成序列优化器 这些微操作,甚至生成一个完整的第二个解释器来执行 他们。

事实上,从 Python 3.13 发布周期的早期开始,所有 CPython 版本都具有 包括这种精确的微操作翻译、优化和执行机制。 但是,默认情况下它是禁用的;甚至优化了口译的开销 对于大多数代码来说,微操作的痕迹太大了。更重的优化 可能也不会有太大的改善,因为任何效率的提高 新的优化可能会被 甚至更小、更复杂的微操作。

克服这一新瓶颈的最明显策略是静态 编译这些优化的跟踪。这提供了避免几个 解释引入的间接和间接费用的来源。特别 它允许消除微操作之间的调度开销(通过替换 具有热代码直线序列的通用解释器)、指令 解码单个微操作的开销(通过“刻录”值或地址 参数、常量和缓存值直接输入到机器指令中), 和内存流量(通过将数据从堆分配的 Python 帧移动到 物理硬件寄存器)。

由于这些数据中的大部分甚至在程序的相同运行和 现有的优化管道大量使用运行时分析信息, 提前编译这些跟踪没有多大意义。一如既往 对于许多其他动态语言(甚至 Python 本身)来说,最有前途的方法是编译 优化了微操作,“及时”执行。

理由

尽管JIT编译器享有盛誉,但它们并不是神奇的“走得更快”的机器。 开发和维护任何类型的优化编译器,即使是单个 平台,更不用说所有 CPython 最受欢迎的支持平台了,是一个 难以置信的复杂,昂贵的任务。使用现有编译器框架 像 LLVM 可以使这个任务更简单,但代价是引入繁重 运行时依赖项和显著增加的 JIT 编译开销。

很明显,在运行时成功编译 Python 代码不仅需要 对正在运行的代码进行高质量的 Python 特定优化,同时也为优化的程序快速生成高效的机器代码。蟒蛇 核心开发团队拥有前者所需的技能和经验(a 中端与解释器紧密耦合),以及复制和补丁编译 为后者提供了一个有吸引力的解决方案。

简而言之,复制和修补允许高质量的模板 JIT 编译器 从用于生成解释器其余部分的同一 DSL 生成。论坛 像 CPython 这样广泛使用的、由志愿者驱动的项目,这种好处是无法实现的 夸大其词:CPython 的维护者,仅仅通过编辑字节码定义, 还将“免费”更新JIT后端,适用于所有JIT支持 平台,一次。无论是否添加指令,这同样正确, 已修改或删除。

与解释器的其余部分一样,JIT 编译器是在构建时生成的, 并且没有运行时依赖项。它支持广泛的平台(请参阅下面的“支持”部分),并且维护负担相对较低。一起 当前的实现由大约 900 行构建时 Python 组成 代码和 500 行运行时 C 代码。

规范

一旦满足以下所有条件,JIT 将变为非实验性 遇到:

它为至少一个流行的产品提供了有意义的性能改进 平台(实际上,大约是 5%)。

它可以在最小的中断下构建、分发和部署。

指导委员会应要求决定提供更多 启用时对社区的价值比禁用时的价值(考虑权衡 如维护负担、内存使用量或替代的可行性 设计)。

这些标准应被视为一个起点,并可以扩展 时间。例如,讨论此 PEP 可能会发现,其他 需求(例如多个承诺的维护者、安全审计、 DevGuide 中的文档、对进程外调试的支持或 用于禁用 JIT) 的运行时选项应添加到此列表中。

在 JIT 是非实验性的之前,它不应该在生产中使用,并且 可能随时损坏或移除,恕不另行通知。

一旦 JIT 不再是实验性的,就应该以大致相同的方式对待它 方式作为其他构建选项,例如 或 . 对于某些平台,它可能是推荐的(甚至是默认的)选项,并发布 经理可以选择在正式版本中启用它。--enable-optimizations--with-lto

支持

JIT 是为 PEP 11 当前的所有一级平台开发的, 其大部分二级平台和三级平台之一。 具体来说,CPython 的分支具有 CI 构建和测试 JIT,用于发布和调试构建:main

  • aarch64-apple-darwin/clang

  • aarch64-pc-windows/msvc 

  • aarch64-unknown-linux-gnu/clang 

  • aarch64-unknown-linux-gnu/gcc 

  • i686-pc-windows-msvc/msvc

  • x86_64-apple-darwin/clang

  • x86_64-pc-windows-msvc/msvc

  • x86_64-unknown-linux-gnu/clang

  • x86_64-unknown-linux-gnu/gcc

值得注意的是,某些平台,甚至是未来的一级平台,可能永远不会 获得 JIT 支持。这可能是由于多种原因,包括不足 LLVM 支持 (),固有的局限性 平台(),或缺乏开发人员的兴趣 ().powerpc64le-unknown-linux-gnu/gccwasm32-unknown-wasi/clangx86_64-unknown-freebsd/clang

添加对平台的 JIT 支持后(即 JIT 构建成功 而不向用户显示警告),它应该以大致相同的方式处理 按照 PEP 11 规定的方式:它应该有可靠的 CI/buildbots 和 JIT 第一层和第二层平台上的故障应阻止发布。虽然是 不需要更新 PEP 11 来指定 JIT 支持,它可能会有所帮助 无论如何都要这样做。否则,应在 JIT 的 README 中维护受支持的平台列表。

由于应该始终可以在没有 JIT 的情况下构建 CPython,因此删除 对平台的 JIT 支持不应被视为向后不兼容 改变。但是,如果这样做是合理的,则正常的弃用过程 应遵循 PEP 387 中概述的内容。

JIT 的构建时依赖项可能会在版本之间更改,在 原因。

向后兼容性

由于当前解释器和 JIT 后端都是 从相同的规范生成,Python 代码的行为应该是 完全没有变化。在实践中,已发现的可观察到的差异 在测试期间修复的往往是现有微操作中的错误 翻译和优化阶段,而不是复制和修补中的错误 步。

调试

分析和调试 Python 代码的工具将继续正常工作。这 包括使用 Python 提供的功能(如 、 或 )的进程内工具,以及 进程外工具,用于从解释器状态遍历 Python 帧。sys.monitoringsys.settracesys.setprofile

但是,目前似乎 C 代码的探查器和调试器是 无法通过 JIT 帧进行回溯。可以使用叶架 (这就是 JIT 本身的调试方式),尽管它的实用性有限,因为 缺少 JIT 帧的正确调试信息。

由于 JIT 发出的代码模板是由 Clang 编译的,因此它可能是 可以通过简单地修改 编译器标志,以便更谨慎地使用帧指针。也可以 收集并发出 Clang 生成的调试信息。这些都不是 已经非常深入地探索了想法。

虽然这是一个应该解决的问题,但解决它并不是一个特别的问题 此时优先级很高。这可能是一个最好由某人探索的问题 与维护 JIT 的人员合作,拥有更多领域专业知识,世卫组织 对这些工具的内部工作原理几乎没有经验。

安全隐患

与任何 JIT 一样,此 JIT 在运行时生成大量可执行数据。 这给 CPython 带来了潜在的新攻击面,因为恶意 因此,能够影响这些数据内容的参与者能够 执行任意代码。这是JIT的一个众所周知的漏洞 编译 器。

为了降低这种风险,JIT 是用最佳实践编写的 介意。特别是,JIT 编译器不会向 程序的其他部分,当它仍然是可写的,并且在任何时候都不是 数据既可写又可执行。

基于模板的 JIT 的性质也严重限制了代码的种类,这些代码 可以生成,进一步降低成功利用的可能性。作为 额外的预防措施,模板本身以静态、只读的方式存储 记忆。

但是,假设不存在可能的漏洞是幼稚的 JIT,尤其是在这个早期阶段。作者不是安全专家, 但可以加入 Python 安全响应团队或与 Python 安全响应团队密切合作 在出现安全问题时对其进行分类和修复。

Apple 芯片

虽然在不实际签署和打包 macOS 版本的情况下很难进行测试, macOS 版本似乎应该为 强化运行时。

这应该不会使安装 Python 变得更加困难,但可能会增加额外的步骤 供发布经理执行。

如何教这个

选择最能描述您的部分:

  • 如果您是 Python 程序员或最终用户...

    • ...对你来说没有任何改变。任何人都不应该分发支持 JIT 的 CPython 解释器,而它仍然是一个实验性功能。一次 它是非实验性的,您可能会注意到性能略好 内存使用率略高。你不应该能够观察任何其他 变化。

  • 如果您维护第三方软件包...

    • ...对你来说没有任何改变。没有 API 或 ABI 更改,JIT 是 不暴露于第三方代码。您不需要更改 CI 矩阵,你不应该能够观察到你的 当启用 JIT 时,包会起作用。

  • 如果分析或调试 Python 代码...

    • ...对你来说没有任何改变。所有 Python 分析和跟踪功能 仍然。

  • 如果对 C 代码进行分析或调试...

    • ...目前,通过 JIT 帧进行跟踪的能力是有限的。这可能 如果需要观察整个 C 调用堆栈,而不是 只是“叶子”框架。有关详细信息,请参阅上面的“调试”部分。

  • 如果您编译自己的 Python 解释器......

    • ...如果您不想构建 JIT,可以简单地忽略它。否则 您需要安装兼容版本的 LLVM,以及 将相应的标志传递给生成脚本。您的构建可能需要长达 多一分钟。请注意,不应将 JIT 分发给最终用户或 在生产中用于仍处于实验阶段。

  • 如果你是 CPython 的维护者(或 CPython 的分支)......

    • ...对你来说没有任何改变。您不需要使用 JIT 进行本地开发 建立。如果选择这样做(例如,帮助重现和会审 JIT 问题),每次 修改相关文件。

    • ...希望你已经对你得到的东西有一个体面的想法 你自己进入。您将定期修改 Python 构建脚本, 用于生成 JIT 的 C 模板,以及实际生成 JIT 的 C 代码 构成 JIT 的运行时部分。您还将处理 各种崩溃,在调试器中单步执行机器代码,盯着 COFF/ELF/Mach-O 转储,在各种平台上开发,以及 通常是更改字节码的人的联系点 当 CI 的 PR 开始失败时(见上文)。理想情况下,您至少熟悉汇编,已经上过几门关于“编译器”的课程 以他们的名义,并阅读了一两篇关于链接器的博客文章

    • ...一般来说,JIT应该不会给您带来太大的不便 (取决于你要做什么)。微操作解释器不是 去任何地方,仍然提供调试体验 今天提供的主要字节码解释器。可能性适中 对解释器进行较大更改(例如添加新的本地 变量、更改错误处理和去优化逻辑,或更改 micro-op 格式)将需要更改用于 生成 JIT,旨在模拟主解释器循环。你 可能偶尔也会倒霉,破坏JIT代码生成, 这将需要您自己修改 Python 构建脚本, 或寻求更熟悉他们的人的帮助(见下文)。

    • ...然后更改字节码定义或主解释器 圈。。。

    • ...而你在 JIT 本身上工作......

    • ...并且您维护 CPython 的其他部分......

被拒绝的想法

在 CPython 之外维护它

虽然可以在 CPython 之外维护 JIT,但其 实现与解释器的其余部分紧密相连, 保持最新状态可能比实际开发更困难 JIT 本身。此外,在现有微操作上工作的贡献者 定义和优化需要修改和构建两个单独的 项目来衡量其在JIT下的变化的影响(而今天, 存在基础设施,可以自动为任何提议的更改执行此操作)。

单独的“JIT”项目的发布可能还需要对应于 特定的 CPython 预发行版和补丁版本,具体取决于具体内容 存在更改。在版本之间提交单个 CPython 可能不会 根本没有相应的 JIT 版本,使调试工作进一步复杂化 (例如平分以查找上游的中断性变化)。

由于 JIT 已经非常稳定,最终目标是让它成为 CPython 的非实验部分,保留它似乎是最好的 前进的道路。话虽如此,相关代码的组织方式是 如果 JIT 最终没有达到其目标,则可以很容易地“删除”它。main

默认打开它

另一方面,一些人建议应该默认启用 JIT 以目前的形式。

同样,重要的是要记住,JIT 并不是“走得更快”的魔术 机器;目前,JIT的速度与现有的专业化差不多 译员。这听起来可能平淡无奇,但实际上是一个相当 重大成就,这也是这种方法的主要原因 被认为足够可行,可以合并以进行进一步开发。main

虽然 JIT 比现有的微操作解释器提供了显着的收益, 当始终启用时,它还不是一个明显的胜利(特别是考虑到它的 增加内存消耗和额外的构建时依赖关系)。这就是 本 PEP 的目的:澄清对客观标准的期望 应该满足才能“拨动开关”。

至少就目前而言,将它放在 中,但默认关闭,似乎是一个 在始终打开它和根本不可用之间有很好的折衷方案。main

支持多个编译器工具链

Clang 是特别需要的,因为它是唯一支持 保证尾部调用 (),这是 CPython 的延续传递式方法所必需的 到JIT编译。没有它,模板之间的尾递归调用可以 导致 C 堆栈无限制增长(并最终溢出)。

由于 LLVM 还包括 JIT 构建过程所需的其他功能 (即用于对象文件解析和反汇编的实用程序)和其他 工具链引入了额外的测试和维护负担,很方便 目前仅支持一个工具链的一个主要版本。

编译基本解释器的字节码

大多数用于复制和修补的现有技术将其用作快速基线 JIT,而 CPython 的 JIT 正在使用该技术来编译优化的微操作跟踪。

在实践中,新的JIT目前位于“基线”和 “优化”其他动态语言运行时的编译器层。这是因为 CPython 使用其专门的自适应解释器来收集运行时分析 信息,用于检测和优化代码中的“热”路径。 此步骤是使用自我修改代码执行的,这种技术非常 使用 JIT 编译器更难实现。

虽然可以使用复制和补丁编译普通字节码(事实上, 早期的原型早于微操作解释器,并且正是这样做的),它 只是似乎没有提供足够的优化潜力,因为更精细 微操作格式。

添加 GPU 支持

JIT 目前仅供 CPU 使用。例如,它不会卸载 NumPy 数组 计算到 CUDA GPU,就像 Numba 这样的 JIT 一样。

已经有一个丰富的工具生态系统来加速这些 专门的任务,而 CPython 的 JIT 并不打算取代它们。相反 它旨在提高通用 Python 代码的性能,即 不太可能从更深层次的 GPU 集成中受益。

未解决的问题

速度

目前,JIT的速度与大多数平台上现有的专业解释器一样快。在这一点上,改善这一点显然是重中之重, 因为提供显着的性能提升是 根本没有JIT。一些拟议的改进已经在进行中,并且 这项正在进行的工作正在GH-115802中跟踪。

记忆

因为它为可执行的机器代码分配了额外的内存,所以 JIT 可以 在运行时使用比现有解释器更多的内存。根据 根据官方基准测试,JIT 目前使用的内存比 基本解释器。 此范围的上限是由于 ,它具有更大的 页面大小(因此,最小分配粒度更大)。aarch64-apple-darwin

但是,这些数字应该作为基准 它们实际上没有非常高的内存使用基线。由于他们 代码与数据的比率越高,JIT的内存开销就越大 比在内存压力更大的典型工作负载中更明显 可能是一个真正的问题。

在优化 JIT 的内存使用方面还没有投入太多精力,因此 这些数字可能代表了一个将随着时间的推移而减少的最大值。 改善这一点是中等优先级,并且正在GH-116017中进行跟踪。

早期版本的 JIT 具有更复杂的内存分配方案 这给 发出代码,并显着膨胀了 Python 的内存占用 可执行。这些问题在当前设计中不再存在。

依赖

生成 JIT 会增加 3 到 60 秒的生成过程,具体取决于 在平台上。只有当生成的文件过时时,它才会重建, 因此,只有那些正在积极开发主解释器循环的人才会 以任何频率重建它。

与 CPython 中的许多其他生成文件不同,JIT 生成的文件不是 由 Git 跟踪。这是因为它们包含编译的二进制代码模板 不仅特定于主机平台,还特定于当前构建配置 对于该平台。因此,托管它们需要大量的工程 努力为每次提交构建和托管数十个大型二进制文件 这会更改生成的代码。虽然可能可行,但这不是优先事项, 因为安装所需的工具对大多数人来说并不是非常困难 人们构建 CPython,构建步骤并不是特别耗时。

版权

本文档属于公有领域或CC0-1.0-Universal 许可证,以更宽松者为准。

The End 微信扫一扫

文章声明:以上内容(如有图片或视频在内)除非注明,否则均为腾龙猫勺儿原创文章,转载或复制请以超链接形式并注明出处。

本文作者:猫勺本文链接:https://www.jo6.cn/post/85.html

上一篇 下一篇

相关阅读

发表评论

访客 访客
快捷回复: 表情:
评论列表 (暂无评论,167人围观)

还没有评论,来说两句吧...

取消
微信二维码
微信二维码
支付宝二维码