web-dev-qa-db-ja.com

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

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リポジトリを検索して、ソースコード内のコメントまたは問題のリスト内の説明を探しましたが、見つかりませんでした。

165
Qantas 94 Heavy

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つのオブジェクト表現を使用します。

  • 辞書モード-オブジェクトはキーとして保存されます-値は ハッシュマップ としてマップします。
  • 高速モード-オブジェクトは structs のように格納され、プロパティアクセスには計算が含まれません。

簡単なデモ は、速度の違いを示しています。ここでは、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.ccSetPrototypeをチェックアウトすると、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は非常に基本的なことを非常に頻繁に行うため、これらのパフォーマンスハックから多くを得ることができます。コールバックと同じくらい高速であるのは簡単ではありません。 まれにライブラリに電力を供給しないコードでこのようなことをしなければなりません。

313