可虚拟化对象

注意: 本文档没有关于如何理解基础知识的适当介绍。我们应该写一些。如果您碰巧在这里并且缺少上下文,请随时在 IRC 上骚扰我们。

问题描述

JIT 非常擅长确保某些对象如果不出现在跟踪之外则永远不会被分配。此类对象称为 virtuals。但是,如果我们正在处理帧,虚拟对象通常不够好。帧可以逸出,并且在我们进入 JIT 时也可能已经被分配。在这种情况下,我们需要一些额外的对象,即使它存在于堆上,仍然可以被优化掉。

解决方案

我们引入了可虚拟化对象。它们是存在于堆上的对象,但其字段并不总是与汇编器中发生的情况同步。一个例子是可虚拟化字段可以存储虚拟对象而不强制它们。这对帧非常有用。声明一个对象可虚拟化就像这样

class Frame(object):
   _virtualizable_ = ['locals[*]', 'stackdepth']

我们在 JitDriver 中这样使用它们

jitdriver = JitDriver(greens=[], reds=['frame'], virtualizables=['frame'])

此声明意味着 stackdepth 是一个可虚拟化的字段,而 locals 是一个可虚拟化的数组(存储在可虚拟化对象上的列表)。关于使用可虚拟化对象,特别是使用可虚拟化数组,有很多规则,可能会让人感到困惑。这些通常会导致编译时错误(而不是奇怪的行为)。规则是

  • 可虚拟化数组必须是固定大小的列表。初始化后(例如在 Frame.__init__ 中),您根本无法调整其大小。您不能将不同的列表分配给该字段,甚至不能传递列表。您只能直接访问 frame.array[index]

  • 每次数组访问都必须使用已知的正索引,该索引不能引发 IndexError。使用 index = jit.hint(index, promote=True) 可能有助于获得常量访问。只有当索引实际上是常量或在用户代码上下文中很少更改时,这才是安全的。

  • 如果您在 JIT 中初始化一个新的可虚拟化对象,则必须像这样完成(例如,如果我们在 Frame.__init__ 中)

    self = hint(self, access_directly=True, fresh_virtualizable=True)
    

    这样您就可以直接填充字段。

  • 如果您在 JIT 之外使用可虚拟化对象——这非常昂贵,有时会导致跟踪中止。请仔细考虑如何只将其用于调试目的,而不是每次都使用(例如,sys._getframe 调用)。

  • 如果您有类似于 Python 生成器的东西,其中可虚拟化对象存活更长时间,则需要在返回之前强制它。这样做比稍后通过外部调用更好。它是使用 jit.hint(frame, force_virtualizable=True) 完成的

  • 您的解释器应该有一个类似于上面 frame 的局部变量。只要它运行其 jit_merge_point 循环,它就不能被修改,并且在循环中它必须直接传递到 jit_merge_point()can_enter_jit() 调用中。如果从每次迭代中的某个地方获取可虚拟化对象,而不是重用相同的未修改局部变量,则已知 JIT 生成器会生成有错误的代码。