标准解释器优化¶
内容
介绍¶
PyPy 标准解释器的一个优势(实际上也是一个激励目标)是它比 CPython 具有更高的灵活性和可配置性。
一个例子是,我们可以提供同一个对象的多个实现(例如列表),而不会对应用程序级代码造成任何差异。这使得为特定情况提供优化的类型专用实现变得容易,而不会影响常规情况下的实现。
本文档描述了几个这样的优化。大多数优化默认情况下是禁用的。此外,对于许多优化来说,它们在实际应用中是否值得(它们确实让一些微基准测试快得多,并且使用更少的内存,但这并不意味着太多)尚不清楚。如果您在这方面有任何观察,请告诉我们!顺便说一句:替代对象实现是进入 PyPy 开发的一个好方法,因为您只需要了解 PyPy 的一小部分就可以完成它们。而且它们也很有趣!
对象优化¶
整数优化¶
缓存小整数¶
与 CPython 类似,可以启用小整数对象的缓存,以避免在进行简单算术运算时每次都进行分配。每次创建一个新的整数对象时,都会检查该整数是否足够小,可以从缓存中检索。
此选项默认情况下是禁用的,您可以使用 –objspace-std-withprebuiltint 选项启用此功能。
整数作为标记指针¶
在使用整数时,节省内存的更激进方法是“小整数”整数实现。它是另一种整数实现,用于只需要 31 位(或在 64 位机器上需要 63 位)的整数。这些整数通过将最低位设置为区分它们与普通指针来表示为标记指针。这完全避免了装箱步骤,从而节省了时间和内存。
您可以使用 –objspace-std-withsmalllong 选项启用此功能。
字典优化¶
列表优化¶
范围列表¶
范围列表解决了 xrange
内置函数解决得不好的同一个问题:range
即使结果列表只用于迭代,也会分配内存。范围列表是列表的不同实现。它们仅作为对 range
的调用的结果而创建。只要结果列表在未被修改的情况下使用,该列表只存储范围的开始、结束和步长。只有当有人修改列表时,才会创建实际的列表。这提供了 xrange
的内存和速度行为以及 range
的通用性,并且使 xrange
基本上变得无用。
此功能默认情况下作为 –objspace-std-withliststrategies 选项的一部分启用。
解释器优化¶
特殊字节码¶
LOOKUP_METHOD & CALL_METHOD¶
Python 面向对象编程版本的一个不寻常特性是“绑定方法”的概念。虽然这个概念很干净也很强大,但对象的分配和初始化并非没有性能成本。我们已经实现了一对字节码来减轻这种成本。
对于给定的方法调用 obj.meth(x, y)
,标准字节码如下所示
LOAD_GLOBAL obj # push 'obj' on the stack
LOAD_ATTR meth # read the 'meth' attribute out of 'obj'
LOAD_GLOBAL x # push 'x' on the stack
LOAD_GLOBAL y # push 'y' on the stack
CALL_FUNCTION 2 # call the 'obj.meth' object with arguments x, y
我们通过将方法查找与方法调用分开来改进这一点,这与其他一些方法不同,但使用值栈作为缓存而不是构建临时对象。我们扩展了字节码编译器以(可选地)为 obj.meth(x, y)
生成以下代码
LOAD_GLOBAL obj
LOOKUP_METHOD meth
LOAD_GLOBAL x
LOAD_GLOBAL y
CALL_METHOD 2
LOOKUP_METHOD
包含与 LOAD_ATTR
完全相同的属性查找逻辑 - 因此完全保留语义 - 但将两个值推送到栈上而不是一个。这两个值是绑定方法对象的“内联”版本:im_func 和 im_self,即分别对应底层 Python 函数对象和对 obj
的引用。这只有在属性实际上引用类中的函数对象时才有可能;当这种情况不成立时,LOOKUP_METHOD
仍然会推送两个值,但其中一个 (im_func) 只是 LOAD_ATTR
本来会返回的常规结果,而另一个 (im_self) 是解释器级别的 None 占位符。
在推送参数后,上述示例中栈的布局如下(栈向上增长)
y (第二个参数) |
x (第一个参数) |
obj (im_self) |
function object (im_func) |
CALL_METHOD N
字节码通过检查 N
个参数下方栈中的 im_self 条目来模拟绑定方法调用:如果它不是 None,则它被认为是调用栈中 im_func 对象的额外第一个参数。
总体影响¶
这些各种优化对性能的影响毫不奇怪地取决于正在运行的程序。使用默认的多字典实现,该实现只是对字符串键字典进行特殊情况处理,在所有基准测试中都是一个明显的胜利,将结果提高了 15-40%。
另一个优化,或者更确切地说是一组优化,具有统一的良好效果,是两个“方法优化”,即方法缓存以及 LOOKUP_METHOD 和 CALL_METHOD 操作码。在一个高度面向对象的基准测试(richards)中,它们结合起来可以使速度提高近 50%,即使在极度非面向对象的 pystone 基准测试中,改进也超过了 20%。
在构建 pypy 时,所有普遍有用的优化默认情况下都处于打开状态,除非你使用 --opt
选项显式降低翻译优化级别。