Bluebirdの util.js
file 、次の機能があります。
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エンジンは、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がこの最適化を行うことを主張するコードを自分で見つけるためにhaveしません。代わりに 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オブジェクトについて学ぶための良い方法なので、今でもお勧めします。
objects.cc
のSetPrototype
をチェックアウトすると、12231行目で呼び出されていることがわかります。
if (value->IsJSObject()) {
JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}
これはFuntionSetPrototype
によって呼び出され、.prototype =
で得られます。
__proto__ =
または.setPrototypeOf
を実行しても機能しますが、これらはES6関数であり、Netscape 7以降のすべてのブラウザーでBluebirdが実行されるため、ここでコードを簡素化することはできません。たとえば、.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%の原因であることを常に覚えておいてください。 Bluebirdは非常に基本的なことを非常に頻繁に行うため、これらのパフォーマンスハックから多くを得ることができます。コールバックと同じくらい高速であるのは簡単ではありません。 まれにライブラリに電力を供給しないコードでこのようなことをしなければなりません。