Bluebird的util.toFastProperties功能如何实现对象的属性"快速"?

时间:2014-07-28 03:00:52

标签: javascript node.js performance v8 bluebird

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

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

出于某种原因,在返回函数之后有一个语句,我不知道它为什么存在。

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

  

'返回'后无法访问'eval'。 (W027)

这个功能到底是做什么的? util.toFastProperties真的使对象的属性“更快”吗?

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

2 个答案:

答案 0 :(得分:312)

2017年更新:首先,对于今天的读者来说 - 这是一个适用于Node 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引擎使用两个对象表示:

  • 字典模式 - 将对象存储为键 - 值映射为hash map
  • 快速模式 - 其中存储的对象类似structs,其中属性访问不涉及任何计算。

以下是显示速度差异的a simple demo。这里我们使用delete语句强制对象进入慢字典模式。

引擎尝试尽可能使用快速模式,通常只要执行大量属性访问 - 但有时它会被抛入字典模式。处于字典模式具有很大的性能损失,因此通常希望将对象置于快速模式中。

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

为什么它更快

在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执行此优化,我们可以改为read the v8 unit tests

// 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中有效。但是 - 很高兴看到它。

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

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);
  }
}

现在,JSObject::MigrateSlowToFast只是显式获取字典并将其转换为快速V8对象。这是一个值得阅读和对v8对象内部的有趣见解 - 但它不是这里的主题。我仍然热烈推荐that you read it here,因为它是了解v8对象的好方法。

如果我们在SetPrototype中查看objects.cc,我们可以看到它在第12231行中被调用:

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

FuntionSetPrototype调用了.prototype =,这是我们__proto__ =所获得的。

执行.setPrototypeOf.setPrototypeOf也会有效,但这些是ES6函数,Bluebird在Netscape 7之后在所有浏览器上运行,因此在这里简化代码是不可能的。例如,如果我们检查// 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, ... ));

{{1}}

所以 - 我们已经从Petka写的代码走向了裸机。这很好。

声明:

请记住这是所有实现细节。像Petka这样的人是优化狂热者。永远记住过早优化是所有邪恶的根源,97%的时间。 Bluebird经常做一些非常基本的事情,所以它从这些性能黑客中获得了很多 - 尽可能快地回调并不容易。您很少必须在不为库提供动力的代码中执行此类操作。

答案 1 :(得分:0)

2021 年的现实(NodeJS 版本 12+)。 似乎完成了巨大的优化,具有删除字段和稀疏数组的对象不会变慢。还是我遗漏了什么?

// 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