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
}
1つまたは2つの小さな最適化以外-以下はすべて有効です。
まず、それが何をするのか、なぜそれが速いのか、そしてなぜそれが機能するのかを説明しましょう。
それがすること
V8エンジンは2つのオブジェクト表現を使用します。
- 辞書モード -オブジェクトがハッシュマップとしてキー値マップとして保存されます。
- 高速モード -オブジェクトが構造体のように格納され、プロパティアクセスに関連する計算は行われません。
これは速度の違いを示す簡単なデモです。ここでは、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で機能することがわかります。ただし、方法を確認しておくとよいでしょう。
チェックする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
明示的にディクショナリを受け取り、それを高速V8オブジェクトに変換します。これは読む価値があり、v8オブジェクトの内部への興味深い洞察ですが、ここでは取り上げません。ここでも、v8オブジェクトについて学ぶのに適した方法であるため、ここを読むことをお勧めします。
でチェックアウトSetPrototype
するobjects.cc
と、行12231で呼び出されていることがわかります。
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
どちらが呼び出されるFuntionSetPrototype
かということは、私たちが得るもの.prototype =
です。
やっ__proto__ =
たり.setPrototypeOf
も働いているだろうが、これらはES6の関数であり、それはここに簡素化コードに問題外ですので、ブルーバードは、ネットスケープ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,
...
));
だから-私たちはペトカが書いたコードからベアメタルへの道を歩んできた。これは良かった。
免責事項:
これはすべての実装の詳細です。ペトカのような人々は、最適化フリークです。時期尚早な最適化が97%の確率ですべての悪の根源であることを常に覚えておいてください。Bluebirdは非常に頻繁に非常に基本的なことを行うので、これらのパフォーマンスハックから多くを得ることができます。あなたはめったにライブラリに電力を供給していないコードでこのような何かを持っていません。