【问题标题】:How do polymorphic inline caches work with mutable types?多态内联缓存如何与可变类型一起使用?
【发布时间】:2009-06-27 15:57:23
【问题描述】:

多态内联缓存(PIC)通过按对象类型缓存实际方法来工作,以避免昂贵的查找过程(通常是哈希表查找)。

如果类型对象是可变的(即方法可能在运行时被猴子修补成不同的东西),如何处理类型比较?

我想出的一个想法是一个“类计数器”,每次调整方法时都会递增,但是这似乎在一个大量猴子修补的环境中会异常昂贵,因为它会杀死所有该类的 PIC,即使它们的方法没有改变。

我确信必须有一个好的解决方案,因为这个问题直接适用于 JavaScript,并且 AFAIK 所有三个大型 JavaScript 虚拟机都有 PIC。

【问题讨论】:

  • 这听起来像是灾难的秘诀。

标签: javascript vm-implementation dynamic-typing cache-invalidation polymorphic-functions


【解决方案1】:

在 V8 中,我假设猴子补丁会改变对象的“隐藏类”(“map”是 SELF 术语)。这会在你猴子修补对象本身时起作用。

如果你用猴子修补类(你能做到这一点是 JS 吗?),我猜它会使所有 PIC 无效,因为这可能很少见。或者,它可能会重新编译旧方法以直接分派到新方法(我猜是在类型检查之后)

顺便说一句,我认为其他“三巨头”实际上并不使用图片。我想你的意思是松鼠鱼和踪迹猴。前者是口译员,后者专注于追踪方法,我不记得听说过任何关于 PIC 的事情。事实上,我不认为 tracemonkey 对对象做任何很酷的事情,但我可能错了。

【讨论】:

    【解决方案2】:

    使用类型推断:

    SpiderMonkey 主要使用类型推断来确定正在访问哪些属性;在类型推断未找到被访问对象的确切形状的情况下,SpiderMonkey 使用 PIC(多态内联缓存)来存储查找结果。

    另一种分析反馈形式是 Baseline JIT 的多态内联缓存。多态内联缓存是一种经典的优化动态调度的技术,起源于 Smalltalk 社区。​​p>

    去优化是类型推断失败时发生的过程的名称:

    当然,JavaScript 是一种动态类型语言,有时关于对象隐藏类的假设是不正确的,在这种情况下,V8 将“去优化”并恢复到方法调用的原始版本检查对象隐藏类。

    为此需要多个编译器:

    从技术上讲,这意味着编译器实际上是两个编译器:一个基本编译器和一个“优化器”。 (或者更多,如果我们谈论的是 JSC 和 SpiderMonkey)。这个概念非常合理,可以产生令人难以置信的性能,但有一个细微差别:优化的代码可能在不同的地方“去优化”,而不仅仅是在入口点,这意味着环境(局部变量、参数、上下文)应该是映射和移动。

    Ion 作为优化编译器,不支持在调试模式检查下编译。事实上,实现这种支持的效用值得怀疑,因为它的优化会使 onStep 调用充其量是不合适的。为了支持调试模式,堆栈上的优化代码被取消优化并被释放到基线。也就是说,堆栈上的 Ion 帧被重建的 Baseline 帧覆盖,该基线帧对应于 Ion 代码当前正在执行的相同位置。

    调试是通过动态反优化完成的:

    我们可以做得更好。 90 年代的梦想,体现在 Self 语言中,在 JavaScript 中是鲜活的。我们可以通过栈上替换技术调整 Self 的动态去优化来调试甚至优化的代码。 debug mode OSR 的核心思想很简单:当需要调试时,我们在堆栈上反优化并重新编译 JIT 代码,同时修补返回地址。如果不是因为它对堆栈帧施加的严格暴力,人们可能会说它几乎是优雅的。

    考虑以下 JavaScript 函数:

    function foo(o) { return o.f + o.g; }
    

    在这个例子中,属性访问可能导致任何事情,从堆中已知位置的简单加载到 getter 的调用,甚至是复杂的 DOM 陷阱,例如 o 是文档对象,f 是页面中元素的名称。基线 JIT 最初将作为完全多态调度执行这些属性访问。但是当它这样做时,它将记录它所采取的步骤,然后将就地修改堆访问以成为必要步骤的缓存,以便在将来重复类似的访问。例如,如果对象有一个属性f在距离对象基础偏移16处,那么代码将被修改为首先快速检查传入的对象是否包含在偏移量16处的属性f,然后执行加载。这些缓存被称为是内联的,因为它们完全表示为生成的机器代码。之所以说它们是多态的,是因为如果遇到不同的对象结构,机器代码会被修改以在进行完全动态的属性查找之前打开先前遇到的对象类型。当 DFG 编译此代码时,它将检查内联缓存是否是单态的——仅针对一个对象结构进行了优化——如果是,它将仅发出对该对象结构的检查,然后直接加载。在此示例中,如果 o 始终是具有属性 fg 且偏移量不变的对象,则 DFG 只需为 o 发出一次类型检查,然后执行两次直接加载。

    参考文献

    【讨论】:

      猜你喜欢
      • 2018-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多