Bluebirdのutil.toFastProperties関数はどのようにしてオブジェクトのプロパティを「高速」にしますか?


165

Bluebirdのutil.jsファイルでは、次の機能があります。

function toFastProperties(obj) {
    /*jshint -W027*/
    function f() {}
    f.prototype = obj;
    ASSERT("%HasFastProperties", true, obj);
    return f;
    eval(obj);
}

何らかの理由で、return関数の後にステートメントがありますが、なぜそこにあるのかはわかりません。

同様に、作者がこれに関するJSHint警告を沈黙させたので、それは意図的であるようです:

「return」の後に到達できない「eval」。(W027)

この関数は正確に何をしますか?util.toFastProperties本当に「速く」オブジェクトのプロパティを作りますか?

BluebirdのGitHubリポジトリを検索して、ソースコードのコメントや問題のリストの説明を探しましたが、何も見つかりませんでした。

回答:


314

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は非常に頻繁に非常に基本的なことを行うので、これらのパフォーマンスハックから多くを得ることができます。あなたはめったにライブラリに電力を供給していないコードでこのような何かを持っていません。


37
これは、私がしばらく読んだ中で最も興味深い投稿です。あなたに多くの敬意と感謝を!
m59 14

2
@timoxleyについてeval(投稿されたコードOPを説明するコードコメントで)「デッドコードの除去またはさらなる最適化によって関数が最適化されないようにします。このコードに到達することはありませんが、到達できないコードであってもv8は最適化されません。関数。" 。これが関連するreadです。この件について、もう少し詳しく説明していただけませんか。
Benjamin Gruenbaum 2014

3
@dherman a 1;は「非最適化」を引き起こさず、debugger;おそらく同じように機能します。良いことはeval、文字列ではない何かが渡されたとき、それはそれで何もしないので、それはどちらかと言えば無害です-一種のif(false){ debugger; }
Benjamin Gruenbaum 14

6
ところで、最近のv8での変更により、このコードは更新されましたが、コンストラクタもインスタンス化する必要があります。だから、それは怠惰になった; d
エサイリヤ2015年

4
@BenjaminGruenbaumこの関数を最適化してはならない理由を詳しく説明できますか?縮小されたコードでは、とにかくevalは存在しません。ここでevalが非縮小コードで役立つのはなぜですか?
Boopathi Rajaa
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.