CPython 性能将提升 5 倍?faster-python 项目 PEP 659 源码级解读

本文深入解读 Python 的 faster-python 计划中的 PEP 659 提案,探讨如何通过 Specializing Adaptive Interpreter 提升 CPython 性能。提案不涉及 JIT,而是通过在 Interpreter 层面进行优化,例如针对 LOAD_GLOBAL 指令的细分,实现字节码级别的性能提升。文章通过源码分析展示了 Warmup、Adaptive、Specializing 和 Deoptimize 四个阶段,展示如何在不影响程序正确性的前提下进行性能优化。
摘要由CSDN通过智能技术生成

作者:修玉同(音弦)

在 2021 年早些时候,Python 作者 Guido van Rossum 被微软返聘继续进行 CPython 相关工作,他们提出了一个 faster-python 计划,计划在 4 年内将 CPython 的性能提升 5 倍,整个项目被开放在 GitHub 的 faster-cpython Group,通过 Activity 可见该项目的一部分 ideas 已经有了相应的代码实现和验证。

本文将就该项目中的一个重要提案 PEP 659 进行解读和源码分析,并从中学习在 bytecode level 进行虚拟机性能优化的思路和手段,希望能对大家有所启发。

提案解读

PEP 659 创建于 2021 年 4 月,全称为 Specializing Adaptive Interpreter,这里有两个关键词:Specializing 和 Adaptive,这里可以简单理解为对特定位置的代码进行适配(Adaptive),替换为特殊的代码(Specializing)从而提高特定位置操作的执行速度。比如通过观察发现某个查询 dict 的代码在多次执行过程中 dict 没有变动,那么我们可以针对这段代码进行优化,将 dict entry 的 index 直接缓存起来,这样在下次查询时就避免了 hashtable 查找的过程从而提高性能,这里的观察就对应到 Adaptive,替换代码的过程则对应到 Specializing。

上面的例子并不准确,只是帮助大家对 Specializing Adaptive Interpreter 有一个初步的印象,下面我们将摘录提案中的关键语句进行解读。

首先要明确的一点是,PEP 659 并不是一个 JIT 方案,因为它的初衷在于让那些无法直接使用 PyPy 等包含 JIT Compiler 的用户也能享受到 faster CPython 的红利。例如在 iOS 平台下,用户进程受限于 codesign 动态创建的可执行的代码页在缺页中断时会因为未包含合法签名而被拒绝,因此无法直接使用包含 JIT Compiler 的 Python 虚拟机。

看到这里可能有些人会担心,不使用 JIT 单纯从虚拟机层面进行优化的空间和收益如何呢?在 PEP 659 中作者也给出了一些解释:

Specialization is typically done in the context of a JIT compiler, but research shows specialization in an interpreter can boost performance significantly, even outperforming a naive compiler.

即研究发现仅仅在 Interpreter 层面进行 Specialization 优化也可以获得显著的性能提升,性能收益甚至可以超过一些初级的 JIT 方案,作者在这里还引用了一篇自己之前的论文,感兴趣的同学可以自行去 PEP 659 提案的参考文献部分查看。

到这里我们也就明确了 PEP 659 不包含 JIT Compiler,简单地说就是它不生成代码,它只是代码的搬运工,我们需要穷举所有可能的优化情况,并且提前准备好代码,在观察到匹配的优化条件时将字节码进行替换,当发现不满足优化条件时还必须能够优雅的退回到优化前的代码以保证程序的正确性。

为了能更好的穷举优化情况和切换代码,需要选择合适的优化粒度,提案原文是:

By using adaptive and speculative specialization at the granularity of individual virtual machine instructions, we get a faster interpreter that also generates profiling information for more sophisticated optimizations in the future.

即在虚拟机指令层面进行优化,而不是像 JIT 那样在一个区域或者函数维度进行优化,这样可以针对特定指令进行细分,例如在 CPython 中获取 globals 和 builtins 都是通过 LOAD_GLOBAL 指令,首先在 globals 中查找,查找失败后再 fallback 到 builtins 中查找,在这里可能的情况只有 2 种,因此我们可以为虚拟机新增两条指令 LOAD_GLOBAL_MODULE 和 LOAD_GLOBAL_BUILTIN,当发现某段字节码中的 LOAD_GLOBAL 一直在查找 globals 时,我们可以将其优化为前者,反之优化为后者,同时可以对 globals 和 builtins dict 的 entry index 进行缓存避免重复访问 dict 的 hashtable,当发现不满足优化条件(例如查找失败,或是 dict 被修改)时再回滚到 LOAD_GLOBAL 指令保证程序的正确性。

上述从 LOAD_GLOBAL 到 LOAD_GLOBAL_MODULE / LOAD_GLOBAL_BUILTIN 的过程实际上就是 PEP 标题中的 Specializing,而选择将指令替换为 LOAD_GLOBAL_MODULE 还是 LOAD_GLOBAL_BUILTIN

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值