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

在Bluebird的util.js文件中 ,它有以下function:

 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仓库search了源代码中的任何评论或者在他们的问题列表中的解释,但我找不到任何。

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 } 

Sans一个或两个小优化 – 以下所有仍然有效。

我们先来讨论它的function,以及为什么这样做更快,为什么这么做。

它能做什么

V8引擎使用两个对象表示法:

  • 字典模式 – 其中对象被存储为键值映射作为哈希映射 。
  • 快速模式 – 在这种模式下 ,像结构一样存储对象,其中属性访问中不涉及任何计算。

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

引擎尽可能使用快速模式,通常在执行大量属性访问时 – 尽pipe有时会被引入字典模式。 在字典模式下有一个很大的性能损失,因此通常希望将对象置于快速模式。

这种攻击是为了强制对象从字典模式进入快速模式。

  • 蓝鸟的佩特卡自己在这里谈论它 。
  • 维亚切斯拉夫·叶戈罗夫(Vyacheslav Egorov)的这些幻灯片也提到了它。
  • 这个问题及其接受的答案也是相关的。
  • 这个稍微过时的文章仍然是一个相当不错的阅读,可以给你一个好主意,如何在V8中存储对象。

为什么它更快

在JavaScript中,原型通常在许多实例之间共享存储函数,并且很lessdynamic地改变。 由于这个原因,使它们处于快速模式以避免每次调用函数时会受到额外的惩罚。

因此,v8会很乐意把函数的.prototype属性的对象放在快速模式下,因为它们将被调用该函数的每个对象作为构造函数共享。 这通常是一个聪明和理想的优化。

怎么运行的

我们先来看一下代码,并确定每一行的function:

 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做了这个优化,我们可以改为阅读v8unit testing :

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

阅读和运行这个testing向我们certificate,这个优化确实在v8中有效。 但是 – 这将是很高兴看到如何。

如果我们检查objects.cc我们可以find以下函数(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只是明确地使用Dictionary并将其转换成一个快速的V8对象。 这是一个有价值的阅读和对v8对象内部的有趣的洞察 – 但这不是这里的主题。 我仍然热烈地build议你在这里阅读,因为这是了解v8对象的好方法。

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

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

而后者又被FuntionSetPrototype调用,这是我们用.prototype =得到的。

__proto__ =或者.setPrototypeOf也可以,但是这些都是ES6的function,自从Netscape 7以后,Bluebird就可以运行在所有的浏览器上,所以在这里简化代码是.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写到裸机的代码中走过了。 这很好。

免责声明:

记住这是所有的实现细节。 像佩特卡这样的人是优化的怪胎。 永远记住,不成熟的优化是所有邪恶97%的时间的根源。 蓝鸟经常做一些非常基本的事情,所以它从这些性能窍门中获得了很多 – callback的速度并不容易。 你很less需要在没有能力的代码中做这样的事情。