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需要在没有能力的代码中做这样的事情。