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 //en.wikipedia.org/wiki/Hash_table.
-
快速模式- 对象存储在其中structs //en.wikipedia.org/wiki/Struct_(C_programming_language),其中属性访问不涉及计算。
Here is 一个简单的演示 //jsperf.com/test-dictionary-mode这表明了速度差异。这里我们使用delete
语句强制对象进入慢速字典模式。
只要有可能,并且通常在执行大量属性访问时,引擎都会尝试使用快速模式 - 但有时它会陷入字典模式。处于字典模式会带来很大的性能损失,因此通常需要将对象置于快速模式。
此 hack 的目的是强制对象从字典模式进入快速模式。
- 蓝鸟的佩特卡本人在这里谈论它 //github.com/petkaantonov/bluebird/wiki/Optimization-killers#52-the-object-being-iterated-is-not-a-simple-enumerable.
-
这些幻灯片(回溯机) //web.archive.org/web/20170729185544/http://mrale.ph/s3/nodecamp.eu/#1维亚切斯拉夫·叶戈罗夫(Vyacheslav Egorov)也提到了这一点。
- 问题“*https://stackoverflow.com/questions/23455678/pros-and-cons-of-dictionary-mode*” https://stackoverflow.com/questions/23455678/pros-and-cons-of-dictionary-mode*%22和它接受的答案也相关。
-
这篇文章有点过时了 //jayconrod.com/posts/52/a-tour-of-v8-object-representation仍然是一本相当不错的读物,可以让您很好地了解 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.
}
我们不have要自己找到代码来断言 v8 进行了此优化,我们可以改为阅读 v8 单元测试 //github.com/v8/v8/blob/d52280b1a7a867ffb350c4f193cf8692861855dd/test/mjsunit/fast-prototype.js:
// 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);
}
}
Now, JSObject::MigrateSlowToFast
只是显式地获取 Dictionary 并将其转换为快速的 V8 对象。这是一本值得一读的书,也是对 v8 对象内部结构的有趣见解——但这不是这里的主题。我还是热烈推荐你在这里读到的 https://github.com/v8/v8/blob/3235f3f8b5930de07a240f61386f21d55040dbf8/src/objects.cc#L4617-L4751因为这是了解 v8 对象的好方法。
如果我们检查一下SetPrototype
in objects.cc
,我们可以看到它在第12231行被调用:
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
依次调用FuntionSetPrototype
这就是我们得到的.prototype =
.
Doing __proto__ =
or .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 经常做一些非常基本的事情,因此它从这些性能黑客中获益匪浅 - 与回调一样快并不容易。你rarely必须在不支持库的代码中执行类似的操作。