在Bluebird的util.js
文件中,它具有以下功能:
function toFastProperties(obj) {
/*jshint -W027*/
function f() {}
f.prototype = obj;
ASSERT("%HasFastProperties", true, obj);
return f;
eval(obj);
}
由于某种原因,在return函数之后有一条语句,我不确定为什么会在其中。
同样,这似乎是故意的,因为作者已对此发出了JSHint警告:
“返回”后无法到达“评估”。(W027)
此功能的作用是什么?难道util.toFastProperties
真的让一个对象的属性“快”?
我已经在Bluebird的GitHub存储库中搜索了源代码中的任何注释或问题列表中的解释,但找不到任何内容。
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引擎使用两种对象表示形式:
这是一个演示速度差异的简单演示。在这里,我们使用该delete
语句强制对象进入慢速字典模式。
引擎会尽可能尝试使用快速模式,通常会在执行大量属性访问时尝试使用快速模式-但是有时会陷入字典模式。处于字典模式会降低性能,因此通常需要将对象置于快速模式。
这种破解旨在迫使对象从字典模式进入快速模式。
- 蓝鸟的佩特卡本人在这里谈论它。
- 维亚切斯拉夫·埃格罗夫(Vyacheslav Egorov)的这些幻灯片也提到了这一点。
- 这个问题及其公认的答案也相关。
- 这篇过时的文章仍然是不错的读物,可以使您对如何将对象存储在v8中有一个很好的了解。
为什么更快
在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中有效。但是-很高兴看到如何。
如果进行检查,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
只需显式获取Dictionary并将其转换为快速的V8对象。这是一本值得一读的书,并且对v8对象内部有一个有趣的见解-但这不是这里的主题。我仍然热烈建议您在这里阅读它,因为这是学习v8对象的好方法。
如果我们签SetPrototype
入objects.cc
,我们可以看到它在12231行中被调用:
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
反过来,FuntionSetPrototype
这就是我们所得到的.prototype =
。
这样做__proto__ =
还是.setPrototypeOf
可以进行的,但是这些是ES6函数,Bluebird自Netscape 7起便在所有浏览器上运行,因此简化此处的代码是不可能的。例如,如果我们检查.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 , javascript , node.js , performance , v8
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!