跟踪优化器¶
用户程序的跟踪不会直接转换为机器码。优化器模块实现了多种不同的语义保持转换,这些转换要么允许从跟踪中删除操作,要么将它们转换为需要更少时间或空间的操作。
优化器位于 rpython/jit/metainterp/optimizeopt/ 中。当您尝试理解此模块时,此页面可能会帮助您入门。
在详细解释某些优化之前,了解跟踪的外观至关重要。优化器带有一个测试套件。它包含许多跟踪示例,您可能需要查看它(在 rpython/jit/metainterp/optimizeopt/test/*.py 中)。允许的操作可以在 rpython/jit/metainterp/resoperation.py 中找到。这是一个跟踪示例
[p0,i0,i1]
label(p0, i0, i1)
i2 = getarrayitem_raw(p0, i0, descr=<Array Signed>)
i3 = int_add(i1,i2)
i4 = int_add(i0,1)
i5 = int_le(i4, 100) # lower-or-equal
guard_true(i5)
jump(p0, i4, i3)
一开始阅读起来可能很笨拙,但当您开始比较构建跟踪的 Python 代码时,它就会变得有意义
from array import array
a = array('i', range(101))
sum = 0; i = 0
while i <= 100: # can be seen as label
sum += a[i]
i += 1
# jumps back to the while header
有更好的方法来计算 [0..100]
的总和,但它比 sum(range(101))
更直观地说明了跟踪是如何构建的。请注意,跟踪语法是在测试套件中使用的语法。它也与 PYPYLOG 在运行时打印的跟踪非常相似。第一行给出输入变量,第二行是 label
操作,最后一行是向后的 jump
操作。
前面提到的这些指令是特殊的
- 输入定义进入跟踪的输入参数类型和名称。
label
是jump
可以定位到的指令。Label 指令具有关联的JitCellToken
,该令牌唯一标识该标签。任何跳转都具有标签的目标令牌。
该令牌保存在指令的所谓 描述符 中。它没有显式写入,因为测试中也没有这样做。但是测试套件为每个跟踪创建一个虚拟令牌,并将其作为描述符添加到 label
和 jump
中。当然,优化器在运行时也会这样做,但使用的是真实的值。示例跟踪在 getarrayitem_raw
中包含描述符。这里它注释了数组的类型。它是一个有符号整数数组。
高级概述¶
在 JIT 后端将任何跟踪转换为机器码之前,它会尝试将跟踪转换为执行速度更快的等效跟踪。方法 optimize_trace(位于 rpython/jit/metainterp/optimizeopt/__init__.py 中)是主要入口点。
优化按顺序依次应用,基本顺序如下
intbounds:rewrite:virtualize:string:earlyforce:pure:heap:unroll
每个冒号分隔的名称都附加了一个类,该类继承自 Optimization 类。Optimizer 类本身也派生自 Optimization 类,并实现优化控制逻辑。大多数优化只需要一次向前传递。跟踪使用 propagate_forward 方法“传播”到每个优化中。逐条指令,它从第一个优化流到最后一个优化。emit_operation 方法被调用用于传递到下一个优化器的每个操作。
intbounds 优化(主要关注优化整数操作)主要使用 基于规则的模式匹配 DSL 实现。
常见模式¶
要查找潜在的优化目标,有必要了解指令类型。简单的解决方案是使用操作编号(=类型)进行切换
for op in operations:
if op.getopnum() == rop.INT_ADD:
# handle this instruction
pass
elif op.getopnum() == rop.INT_FLOOR_DIV:
pass
# and many more
如果您开始匹配参数(第一个参数是常量,第二个参数是变量,反之亦然?),情况会变得更糟。解决此代码膨胀的模式是使用 make_dispatcher_method 将其移动到单独的方法中。它将方法与指令类型相关联
class OptX(Optimization):
def prefix_INT_ADD(self, op):
pass # emit, transform, ...
dispatch_opt = make_dispatcher_method(OptX, 'prefix_',
default=OptX.emit_operation)
OptX.propagate_forward = dispatch_opt
optX = OptX()
for op in operations:
optX.propagate_forward(op)
propagate_forward
搜索能够处理指令类型的方法。例如,INT_ADD 将调用 prefix_INT_ADD。如果没有该指令的函数,它将路由到默认实现(在此示例中为 emit_operation
)。
重写优化¶
第二个优化称为“重写”,通常也称为强度削弱。一个简单的例子是,一个整数乘以 2 等于将位左移一次(例如 x * 2 == x << 1
)。此优化不仅执行强度削弱,还执行布尔或算术简化。其他示例包括:x & 0 == 0
,x - 0 == x
每当遇到此类操作时(例如 y = x & 0
),不会发出任何操作。相反,变量 y 被设为等于 0(= make_constant_int(op, 0)
)。在跟踪中找到的变量是可以在 rpython/jit/metainterp/history.py 中找到的类的实例。当一个值被设为等于另一个值时,它的盒会被设置为指向另一个值。
纯优化¶
“纯”优化交织在基本优化器中。它保存操作、结果、参数以使其具有纯语义。
这里的“纯”与 jit.elidable
装饰器相同:没有“可观察的”副作用且是引用透明的(操作可以用其结果替换而不会更改程序语义)。在 resoperation.py 中标记为 ALWAYS_PURE 的操作是 NOSIDEEFFECT 操作的子集。诸如 new、new array、getfield_(raw/gc) 等操作被标记为 NOSIDEEFFECT,但未标记为 ALWAYS_PURE。
纯操作以两种不同的方式进行优化。如果其参数是常量,则删除该操作并将结果转换为常量。如果不是,我们仍然可以使用记忆化技术:如果稍后我们再次看到对相同参数的相同操作,则无需重新计算其结果,而只需重用先前操作的结果。
展开优化¶
可以在文档 PyPy 的跟踪 JIT 中的循环感知优化 中找到详细说明
此优化不属于仅一次向前传递的传统方案。简而言之,它_一次_展开跟踪,连接这两个跟踪(通过将参数插入剥离跟踪的跳转和标签中),并使用信息来消除分配、传播常量并执行“optimizeopt”模块中当前存在的任何其他优化。
它位于所有优化之前,因此扩展了 Optimizer 类并在继续之前展开一次循环。
本文档缺少哪些内容¶
- 未解释保护
- 未解释几种优化