2017年更新:首先,对于今天即将到来的读者 - 这是一个与节点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
引擎会尽可能尝试使用快速模式,并且通常在执行大量属性访问时 - 但是有时它会被扔到字典模式中。处于字典模式会有很大的性能损失,因此通常需要将对象置于快速模式。
此黑客旨在强制对象从字典模式进入快速模式。
为什么它更快
在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。但是-很高兴看到如何。
如果我们检查,我们可以找到以下函数(L9925):objects.cc
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);
}
}
现在,只需显式获取字典并将其转换为快速的 V8 对象。这是一本值得一读的书,也是对 v8 对象内部的有趣见解 - 但这不是这里的主题。我仍然强烈建议您在这里阅读它,因为这是了解v8对象的好方法。JSObject::MigrateSlowToFast
如果我们签出 ,我们可以看到它在行 12231 中被调用:SetPrototype
objects.cc
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
这反过来又被称为,这就是我们得到的。FuntionSetPrototype
.prototype =
做或也会工作,但这些是ES6函数,Bluebird自Netscape 7以来在所有浏览器上运行,所以这是不可能的,可以简化这里的代码。例如,如果我们检查,我们可以看到:__proto__ =
.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编写的代码到裸机的道路。这很好。
免責聲明:
请记住,这都是实现细节。像Petka这样的人是优化怪胎。永远记住,过早的优化是97%的情况下所有邪恶的根源。Bluebird经常做一些非常基本的事情,所以它从这些性能技巧中获得了很多 - 像回调一样快并不容易。您很少需要在不能为库提供支持的代码中执行此类操作。