1. ホーム
  2. javascript

[解決済み] Bluebirdのutil.toFastProperties関数は、どのようにしてオブジェクトのプロパティを「高速化」するのでしょうか?

2022-04-23 19:02:22

質問

ブルーバードの 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リポジトリで、ソースコードにコメントがないか、課題リストに説明がないか探したのですが、見つかりませんでした。

解決方法は?

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 は、明示的にDictionaryを受け取り、高速なV8オブジェクトに変換しているだけです。この本は読み応えがあり、V8オブジェクトの内部を知る上で興味深いものですが、ここでの主題ではありません。私は今でも をご覧ください。 V8オブジェクトについて学ぶには良い方法だと思います。

をチェックアウトすると SetPrototypeobjects.cc で、12231行目で呼び出されていることがわかります。

if (value->IsJSObject()) {
    JSObject::OptimizeAsPrototype(Handle<JSObject>::cast(value));
}

で呼び出され、その後に FuntionSetPrototype で得られるものです。 .prototype = .

行うこと __proto__ = または .setPrototypeOf でもよかったのですが、これらはES6の関数で、BluebirdはNetscape 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は非常に基本的なことを非常に頻繁に行うので、これらのパフォーマンスハックから多くの利益を得ます - コールバックと同じくらい速くなるのは簡単ではありません。あなたは めったに このようなことは、ライブラリに頼らないコードで行う必要があります。