奇技淫巧学 V8 之四,迁移对象至快速模式

876 阅读3分钟

先划重点:使用奇技淫巧,要知其然并知其所以然,这样就算 V8 更新也可以找出解决方案。

在第一章 《奇技淫巧学 V8 之一,对象访问模式优化》 中我们提出将对象由字典模式优化成为快速模式有如下两种方案:

  • 调用 Runtime Call (%ToFastProperties) 。
  • 设置成为一个函数(或对象)的原型。

但是为什么通过上述两(奇)种(技)操(淫)作(巧)可以优化对象为快速模式了?

我们要先从 Runtime Call (%ToFastProperties) 说起,在 V8 内部其实是直接调用了 v8::internal::JSObject::MigrateSlowToFast 方法,但这方法的 namespace 为 v8::internal 无法直接在外部(包括 JavaScript 与 外部 C++ 代码)进行调用。

虽然我们不能直接进行调用,但是我们可以通过一些方法的 side effects 来隐式调用,而将对象设置成为一个函数(或对象)的原型就是运用了这个技术。

在 V8 中有如上图的途径可以隐式调用到 MigrateSlowToFast 方法,而我们的奇技淫巧也正是利用了这条通路。

当 StoreIC(LoadIC)处于非初态时,每次 IC 状态的变更 V8 均会调用 MakePrototypesFast,最终会调用到 MigrateSlowToFast 将处于字典模式的对象优化成为快速模式。

IC 相关知识详见第三章《奇技淫巧学 V8 之三,多态内联缓存 PICs》

我们来具体分析下之前的 MagicFunc:

function MagicFunc(obj) {
    function FakeConstructor() {
        this.x = 0; // StoreIC(x)
    }

    // 拷贝 <Map> 作为 prototype, 不再与其它对象共享
    FakeConstructor.prototype = obj;

    // StoreIC(x) state == UNINITIALIZED
    new FakeConstructor();
    // StoreIC(x) state == PREMONOMORPHIC

    new FakeConstructor();
    // StoreIC(x) state == MONOMORPHIC
    // 隐式调用 MakePrototypesFast, 由 slow 到 fast 迁移
};
  • FakeConstructor 构造器中的属性存取表达式引导 V8 创建一个 StoreIC(x) 。
  • 将目标对象作为 FakeConstructor 的原型(prototype)。
  • 第一次实例化 FakeConstructor 时 StoreIC(x) 由 初态 迁移到 单态前置,但由于 IC 的初始状态为 初态 故 V8 不会优化目标对象。
  • 第二次实例化 FakeConstructor 时 StoreIC(x) 由 单态前置 迁移到 单态,由于 IC 的初始状态为 单态前置 故 V8 会隐式调用 MigrateSlowToFast 优化目标对象。

仅把对象设置成为另外一个函数(对象)的原型(并不实例化或调用)时,此对象的 <Map> 会被拷贝,不再与其它(相同构建结构的)对象共享一个 <Map> 。

// flags: --allow-natives-syntax
var a = {x : 1};
var b = {x : 1};
%HaveSameMap(a, b);  // true, 由于它们拥有相同构建结构

var FakeConstructor = function() {};
FakeConstructor.prototype = b;
%HaveSameMap(a, b);  // false, 由于 b 被设置成为了 FakeConstructor 的 prototype
通过 Runtime Call (%HaveSameMap) 来判断两个 JSObject<Map> 是否相同。

我们发现, V8 隐式调用 MigrateSlowToFast 的路径,不仅可以通过创建 StoreIC(也就是 MagicFunc)的方法实现,还可以通过创建 LoadIC 来实现。

下面就是其一种实现:

function MagicFunc_Alt(obj) {
    var fakeObj = {};
    function FakeLoad() {
        fakeObj.x; // LoadIC(x), 即使 x 不存在
    }

    // CopyAsPrototype
    fakeObj.__proto__ = obj;

    // LoadIC(x) state == UNINITIALIZED
    FakeLoad();
    // LoadIC(x) state == PREMONOMORPHIC
    FakeLoad();
    // LoadIC(x) state == MONOMORPHIC
    // 隐式调用 MakePrototypesFast, 由 slow 到 fast 迁移
}

原理与效果同 MagicFunc 类似就不重述了。

了解完它们的原理后,我们还可以写出很多类似的 MagicFunc ,所以使用奇技淫巧,不仅要知其然,还需知其所以然,才能举一反三,就算 V8 更新也可以找出解决方案。

在实际业务中,有时 delete 操作难以避免,有没有办法删除属性又不让对象退化到字典模式(而不用退化之后再去优化了)?欢迎收看下一章: