30

ベンチマーク:

JsPerf

不変量:

var f = function() { };

var g = function() { return this; }

テスト:

予想される速度の順に以下

  • new f;
  • g.call(Object.create(Object.prototype));
  • new (function() { })
  • (function() { return this; }).call(Object.create(Object.prototype));

実際の速度:

  1. new f;
  2. g.call(Object.create(Object.prototype));
  3. (function() { return this; }).call(Object.create(Object.prototype));
  4. new (function() { })

質問:

  1. インライン匿名関数fを交換する場合。(テスト4.)テストが遅いgのはなぜですか?new

アップデート:

インライン化されている場合、具体的に何が原因でnewが遅くなります。fg

ES5仕様への参照、またはJagerMonkeyまたはV8ソースコードへの参照に興味があります。(JSCとCarakanのソースコードも自由にリンクしてください。ああ、IEチームは必要に応じてChakraソースをリークする可能性があります)。

JSエンジンのソースをリンクする場合は、それを説明してください。

4

5 に答える 5

16

#4 と他のすべてのケースの主な違いは、コンストラクターとしてクロージャーを初めて使用するときは常に非常にコストがかかることです。

  1. これは常に (生成されたコードではなく) V8 ランタイムで処理され、コンパイルされた JS コードと C++ ランタイムの間の移行にはかなりのコストがかかります。その後の割り当ては、通常、生成されたコードで処理されます。Generate_JSConstructStubHelperinを見るbuiltins-ia32.ccと、Runtime_NewObjectwhen クロージャに初期マップがないことがわかります。( http://code.google.com/p/v8/source/browse/trunk/src/ia32/builtins-ia32.cc#138を参照)

  2. クロージャーがコンストラクターとして初めて使用されるとき、V8 は新しいマップ (別名隠しクラス) を作成し、それをそのクロージャーの初期マップとして割り当てる必要があります。http://code.google.com/p/v8/source/browse/trunk/src/heap.cc#3266を参照してください。ここで重要なことは、マップが別のメモリ空間に割り当てられることです。このスペースは、高速部分スカベンジコレクターではクリーニングできません。マップ スペースがオーバーフローすると、V8 は比較的コストのかかるフルマーク スイープGC を実行する必要があります。

クロージャーをコンストラクターとして初めて使用するときに他にもいくつかのことが起こりますが、1 と 2 がテスト ケース 4 の速度低下の主な原因です。

式 #1 と #4 を比較すると、違いは次のとおりです。

  • #1 毎回新しいクロージャーを割り当てるわけではありません。
  • #1は毎回実行時に入るわけではありません。クロージャーが取得された後、最初のマップ構築は生成されたコードの高速パスで処理されます。生成されたコードで全体の構造を処理することは、ランタイムと生成されたコードの間を行き来するよりもはるかに高速です。
  • #1 は、毎回新しいクロージャーごとに新しい初期マップを割り当てるわけではありません。
  • #1は、マップスペースのオーバーフローによるマークスイープを引き起こしません(安価なスカベンジのみ)。

#3と#4を比較すると、違いは次のとおりです。

  • #3は、毎回新しいクロージャーごとに新しい初期マップを割り当てるわけではありません。
  • #3は、マップスペースのオーバーフローによるマークスイープを引き起こしません(安価なスカベンジのみ)。
  • #4 は JS 側ではあまり行いません (Function.prototype.call、Object.create、Object.prototype ルックアップなどはありません) C++ 側ではより多くのことを行います (#3 も Object.create を実行するたびにランタイムに入りますが、そこではほとんど何もしません) .

ここでの結論は、V8 がいくつかの配管をセットアップする必要があるため、同じクロージャーの後続の構築呼び出しと比較して、コンストラクターとしてクロージャーを初めて使用するときはコストがかかるということです。クロージャーをすぐに破棄すると、基本的に、後続のコンストラクター呼び出しを高速化するために V8 が行ったすべての作業が破棄されます。

于 2011-06-25T14:47:24.627 に答える
5

問題は、さまざまなエンジンの現在のソース コードを検査できることですが、あまり役に立ちません。コンパイラを裏切ろうとしないでください。とにかく、最も一般的な使用法に合わせて最適化しようとします。(function() { return this; }).call(Object.create(Object.prototype))1,000回呼び出されることに実際のユースケースがあるとはまったく思いません。

「プログラムは人が読めるように書かれるべきであり、機械が実行するのは偶然に限られるべきです。」

Abelson & Sussman、SICP、初版の序文

于 2011-06-24T21:02:19.650 に答える
3

次の展開は、V8 で何が起こっているかを説明していると思います。

  1. t(exp1) : t(オブジェクト作成)
  2. t(exp2) : t(Object.create()によるオブジェクト生成)
  3. t(exp3) : t(Object.create()によるオブジェクト作成) + t(関数オブジェクト作成)
  4. t(exp4) : t(オブジェクト作成) + t(関数オブジェクト作成) + t(クラスオブジェクト作成)[Chromeの場合]

    • Chrome の隠しクラスについては、http ://code.google.com/apis/v8/design.html を参照してください。
    • 新しい Object が Object.create によって作成されるとき、新しい Class オブジェクトを作成する必要はありません。オブジェクトリテラルに使用されるものが既にあり、新しいクラスは必要ありません。
于 2011-06-23T18:17:19.333 に答える
0
new f;
  1. ローカル関数 'f' (ローカル フレームのインデックスによるアクセス) を取る - 安価。
  2. バイトコード BC_NEW_OBJECT (またはそのようなもの) を実行します - 安価です。
  3. 関数を実行します - ここで安いです。

今これ:

g.call(Object.create(Object.prototype));
  1. グローバル変数を見つけるObject- 安い?
  2. prototypeオブジェクト内のプロパティを検索- まあまあ
  3. createオブジェクト内のプロパティを検索- まあまあ
  4. ローカル var g を検索します。- 安いです
  5. 物件を探すcall-まあまあ
  6. create関数の呼び出し- まあまあ
  7. call関数の呼び出し- まあまあ

この:

new (function() { })
  1. 新しい関数オブジェクト (その無名関数) を作成します - 比較的高価です。
  2. バイトコードを実行する BC_NEW_OBJECT - 安い
  3. 関数を実行します - ここで安いです。

ご覧のとおり、ケース #1 が最も消費量が少ないです。

于 2011-06-24T21:26:34.597 に答える
0

これら 2 つの呼び出しは、まったく同じことを行うわけではありません。このケースを考えてみましょう:

var Thing = function () {
  this.hasMass = true;
};
Thing.prototype = { holy: 'object', batman: '!' };
Thing.prototype.constructor = Thing;

var Rock = function () {
  this.hard = 'very';
};
Rock.prototype = new Thing();
Rock.constructor = Rock;

var newRock = new Rock();
var otherRock = Object.create(Object.prototype);
Rock.call(otherRock);

newRock.hard // => 'very'
otherRock.hard // => 'very'
newRock.hasMass // => true
otherRock.hasMass // => undefined
newRock.holy // => 'object'
otherRock.holy // => undefined
newRock instanceof Thing // => true
otherRock instanceof Thing // => false

したがって、呼び出しによってプロトタイプから継承されRock.call(otherRock)ないことがわかります。otherRockこれは、追加された速度低下の少なくとも一部を説明する必要があります。私のテストではnew、この単純な例でも、コンストラクトはほぼ 30 倍遅くなります。

于 2011-06-23T15:41:21.213 に答える