奇技淫巧学 V8 之一,对象访问模式优化

2,182 阅读2分钟

先划重点:要拥有最高的性能,尽量让对象处于快速模式

一切的一切先从一段 benchmark 开始说起:

setup block:

function createObjects() {
    return [
        {x: 1, y: 2, z: 3}, 
        {a: 1, b: 2, c: 3}
    ];
}
function test(obj) {
    var sum = 0;
    for (var i = 0; i < 100; i++) {
        sum += obj.a + obj.c;
    }
    return sum;
}

case 1:pair[1] 没有 delete 操作

var pair = createObjects();
delete pair[0].y;
test(pair[1]);

case 2:pair[1] 存在 delete 操作

var pair = createObjects();
delete pair[1].b;
test(pair[1]);

通过测试我们可以发现没有 delete 操作的比存在 delete 操作的对象访问速度快 3.4x 左右。

要解释这个现象,我们就要先了解 V8 对于 JavaScript 对象的两种访问模式:

  • Dictionary(Slow) Mode:字典模式也称为哈希表模式,V8 使用哈希表来存储对象的属性。
  • Stable(Fast) Mode:使用类似数组(C Struct)结构来存储对象的属性并使用 Offset 进行访问。

新创建的小对象为快速模式(Fast Mode),当执行如下操作时会退化成为字典模式(Dictionary Mode):

  • 动态添加过多的属性
  • 删除属性(delete)
    • 删除非最后添加的属性(V8 >= 6.0)

也就是说当对象被当作哈希表使用时(如存储大量数据),他就会退化到字典模式。

在开发调试过程中,可以调用 V8 的 RuntimeCall (开启 --allow-natives-syntax)来判断与优化对象当前的状态:

  • %HasFastProperties(Object): 判断对象当前是否处于快速模式下。
  • %ToFastProperties(Object):强制优化对象到快速模式。

我们来看个例子:

// flags: --allow-natives-syntax
var obj = {x : 1, y : 2};

%HasFastProperties(obj); // true  : 新对象为快速模式

delete obj.x;

%HasFastProperties(obj); // false : 删除(非最后一个添加的)属性退化为字典模式

%ToFastProperties(obj);

%HasFastProperties(obj); // true  : 调用 RuntimeCall 强制优化为快速模式

for (var i = 0; i < 100; i++) {
	obj['arg' + i] = i;
}

%HasFastProperties(obj); // false : 动态添加过多的属性退化为字典模式

如上文所述,在开发环境中可以调用 RuntimeCall(%ToFastProperties)将处于字典模式的对象优化成为快速(Fast)模式。

而在运营环境中,当对象被设置成为一个函数(或对象)的原型时也会从字典模式优化成为快速(Fast) 模式。

function MagicFunc(obj) {
    function FakeConstructor() {
        this.x = 0;
    }
    FakeConstructor.prototype = obj;
    new FakeConstructor();
    new FakeConstructor();
};

// flags: --allow-natives-syntax
var obj = {x : 1, y : 2};

delete obj.x;

%HasFastProperties(obj);
// false : 删除(非最后添加的)属性,退化到字典模式

MagicFunc(obj);

%HasFastProperties(obj);
// true  : !magic!

调用 MagicFunc 后对象由字典模式优化为快速模式,这不是魔法!

要了解背后的原因,欢迎收看下一章: