Bluebird的util.toFastProperties函数如何使对象的属性“快速”?

2022-08-30 02:06:52

在 Bluebird 的 util.js 文件中,它具有以下功能:

function toFastProperties(obj) {
    /*jshint -W027*/
    function f() {}
    f.prototype = obj;
    ASSERT("%HasFastProperties", true, obj);
    return f;
    eval(obj);
}

出于某种原因,return函数后面有一个语句,我不确定为什么它在那里。

同样,这似乎是故意的,因为作者已经沉默了JSHint对此的警告:

“返回”后无法访问的“eval”。(W027)

这个函数到底有什么作用?是否真的使对象的属性“更快”?util.toFastProperties

我已经在Bluebird的GitHub存储库中搜索了源代码中的任何评论或问题列表中的解释,但我找不到任何内容。


答案 1

2017年更新:首先,对于今天即将到来的读者 - 这是一个与节点7(4 +)一起使用的版本:

function enforceFastProperties(o) {
    function Sub() {}
    Sub.prototype = o;
    var receiver = new Sub(); // create an instance
    function ic() { return typeof receiver.foo; } // perform access
    ic();
    ic();
    return o;
    eval("o" + o); // ensure no dead code elimination
}

没有一两个小的优化 - 以下所有内容仍然有效。


让我们首先讨论它的作用以及为什么它更快,然后为什么它有效。

它的作用

V8 引擎使用两种对象表示形式:

  • 字典模式 - 其中对象存储为键 - 值映射为哈希映射
  • 快速模式 - 其中对象像结构一样存储,其中不涉及属性访问的计算。

下面是一个简单的演示,演示了速度差异。在这里,我们使用该语句强制对象进入慢速字典模式。delete

引擎会尽可能尝试使用快速模式,并且通常在执行大量属性访问时 - 但是有时它会被扔到字典模式中。处于字典模式会有很大的性能损失,因此通常需要将对象置于快速模式。

此黑客旨在强制对象从字典模式进入快速模式。

为什么它更快

在JavaScript原型中,原型通常存储许多实例之间共享的函数,并且很少动态更改很多。因此,非常希望将它们置于快速模式,以避免每次调用函数时都会受到额外的惩罚。

为此,v8 将很乐意将作为函数属性的对象置于快速模式,因为它们将由通过调用该函数作为构造函数而创建的每个对象共享。这通常是一个聪明而理想的优化。.prototype

它是如何运作的

让我们首先浏览代码并弄清楚每行的作用:

function toFastProperties(obj) {
    /*jshint -W027*/ // suppress the "unreachable code" error
    function f() {} // declare a new function
    f.prototype = obj; // assign obj as its prototype to trigger the optimization
    // assert the optimization passes to prevent the code from breaking in the
    // future in case this optimization breaks:
    ASSERT("%HasFastProperties", true, obj); // requires the "native syntax" flag
    return f; // return it
    eval(obj); // prevent the function from being optimized through dead code
    // elimination or further optimizations. This code is never
    // reached but even using eval in unreachable code causes v8
    // to not optimize functions.
}

我们不必自己找到代码来断言v8进行了这种优化,我们可以阅读v8单元测试

// Adding this many properties makes it slow.
assertFalse(%HasFastProperties(proto));
DoProtoMagic(proto, set__proto__);
// Making it a prototype makes it fast again.
assertTrue(%HasFastProperties(proto));

阅读并运行此测试向我们表明,此优化确实适用于 v8。但是-很高兴看到如何。

如果我们检查,我们可以找到以下函数(L9925):objects.cc

void JSObject::OptimizeAsPrototype(Handle<JSObject> object) {
    if (object->IsGlobalObject()) return;

    // Make sure prototypes are fast objects and their maps have the bit set
    // so they remain fast.
    if (!object->HasFastProperties()) {
        MigrateSlowToFast(object, 0);
    }
}

现在,只需显式获取字典并将其转换为快速的 V8 对象。这是一本值得一读的书,也是对 v8 对象内部的有趣见解 - 但这不是这里的主题。我仍然强烈建议您在这里阅读它,因为这是了解v8对象的好方法。JSObject::MigrateSlowToFast

如果我们签出 ,我们可以看到它在行 12231 中被调用:SetPrototypeobjects.cc

if (value->IsJSObject()) {
    JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}

这反过来又被称为,这就是我们得到的。FuntionSetPrototype.prototype =

做或也会工作,但这些是ES6函数,Bluebird自Netscape 7以来在所有浏览器上运行,所以这是不可能的,可以简化这里的代码。例如,如果我们检查,我们可以看到:__proto__ =.setPrototypeOf.setPrototypeOf

// ES6 section 19.1.2.19.
function ObjectSetPrototypeOf(obj, proto) {
    CHECK_OBJECT_COERCIBLE(obj, "Object.setPrototypeOf");

    if (proto !== null && !IS_SPEC_OBJECT(proto)) {
        throw MakeTypeError("proto_object_or_null", [proto]);
    }

    if (IS_SPEC_OBJECT(obj)) {
        %SetPrototype(obj, proto); // MAKE IT FAST
    }

    return obj;
}

哪个直接在:Object

InstallFunctions($Object, DONT_ENUM, $Array(
...
"setPrototypeOf", ObjectSetPrototypeOf,
...
));

因此-我们已经走过了从Petka编写的代码到裸机的道路。这很好。

免責聲明:

请记住,这都是实现细节。像Petka这样的人是优化怪胎。永远记住,过早的优化是97%的情况下所有邪恶的根源。Bluebird经常做一些非常基本的事情,所以它从这些性能技巧中获得了很多 - 像回调一样快并不容易。您很少需要在不能为库提供支持的代码中执行此类操作。


答案 2

2021年的现实(NodeJS版本12+)。似乎已经完成了巨大的优化,具有已删除字段和稀疏数组的对象不会变慢。或者我错过了smth?

// run in Node with enabled flag
// node --allow-natives-syntax script.js

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var obj1 = new Point(1, 2);
var obj2 = new Point(3, 4);
delete obj2.y;

var arr = [1,2,3]
arr[100] = 100

console.log('obj1 has fast properties:', %HasFastProperties(obj1));
console.log('obj2 has fast properties:', %HasFastProperties(obj2));
console.log('arr has fast properties:', %HasFastProperties(arr));

两者都显示真实

obj1 has fast properties: true
obj2 has fast properties: true
arr has fast properties: true