4

編集!多くのフォローアップ調査で、私の質問に対する単純な答えがないことが示された後、私は自分自身の答えに変更しました。下記参照!

そのため、前回の質問のフォローアップとして、パフォーマンスを最適化するための Javascript のベスト プラクティスをよりよく理解しようとしています。次の例では、ブラウザー内プロファイラーを使用して Chrome 28.0.1500.70 でテストしています。

1 秒間に数百 k 回呼び出されるオブジェクトにカプセル化された数学関数がいくつかあり、実行時間を少し短縮しようとしています。

親オブジェクトのローカル コピーを呼び出された関数自体のローカルとして作成することで、既にいくつかの最適化を行っており、適切な (~16%) パフォーマンスの向上が得られました。ただし、親オブジェクトから別の関数を呼び出すために同じことを行ったところ、パフォーマンスが大幅に (~100%) 向上しました。

元のセットアップは、this.cirInd を介して仲間の親オブジェクト関数 cirInd を呼び出す calcNeighbors でした。

cirInd のローカル var コピーを作成し、それを呼び出すと、パフォーマンスが大幅に向上し、以前の calcNeighbors の実行時間の半分以下になりました。

ただし、cirInd を calcNeighbors のインライン関数にすると、親オブジェクトから呼び出した場合と同じようにパフォーマンスが低下しました。

これには本当に困惑しています。これは Chrome のプロファイラーの癖かもしれませんが (2 番目のケースでは cirInd がまったく表示されません)、ケース 2 を使用すると、アプリケーションのパフォーマンスが確実に向上します。

ケース 2 がケース 1 よりもはるかに高速である理由を誰かが説明できますが、さらに重要なのは、ケース 3 ではパフォーマンスが向上しないように見える理由です。

問題の関数は次のとおりです。

親オブジェクトからの呼び出し:

  window.bgVars = {
     <snip>
     "cirInd": function(index, mod){
        //returns modulus, array-wrapping value to implement circular array
        if(index<0){index+=mod;}
        return index%mod;
     },
     "calcNeighbors": function(rep){
        var foo = this.xBlocks;
        var grid = this.cGrid;
        var mod = grid.length;
        var cirInd = this.cirInd;
        var neighbors = grid[this.cirInd(rep-foo-1, mod)] + grid[this.cirInd(rep-foo, mod)] + grid[this.cirInd(rep-foo+1, mod)] + grid[this.cirInd(rep-1, mod)] + grid[this.cirInd(rep+1, mod)] + grid[this.cirInd(rep+foo-1, mod)] + grid[this.cirInd(rep+foo, mod)] + grid[this.cirInd(rep+foo+1, mod)];
        return neighbors;
     },
     <snip>
  }

ローカル変数による呼び出し:

  window.bgVars = {
     <snip>
     "cirInd": function(index, mod){
        //returns modulus, array-wrapping value to implement circular array
        if(index<0){index+=mod;}
        return index%mod;
     },
     "calcNeighbors": function(rep){
        var foo = this.xBlocks;
        var grid = this.cGrid;
        var mod = grid.length;
        var cirInd = this.cirInd;
        var neighbors = grid[cirInd(rep-foo-1, mod)] + grid[cirInd(rep-foo, mod)] + grid[cirInd(rep-foo+1, mod)] + grid[cirInd(rep-1, mod)] + grid[cirInd(rep+1, mod)] + grid[cirInd(rep+foo-1, mod)] + grid[cirInd(rep+foo, mod)] + grid[cirInd(rep+foo+1, mod)];
        return neighbors;
     },
     <snip>
  }

インライン呼び出し:

  window.bgVars = {
     <snip>
     "calcNeighbors": function(rep){
        var foo = this.xBlocks;
        var grid = this.cGrid;
        var mod = grid.length;
        function cirInd(index, mod){
          //returns modulus, array-wrapping value to implement circular array
          if(index<0){index+=mod;}
          return index%mod;
        }
        var neighbors = grid[cirInd(rep-foo-1, mod)] + grid[cirInd(rep-foo, mod)] + grid[cirInd(rep-foo+1, mod)] + grid[cirInd(rep-1, mod)] + grid[cirInd(rep+1, mod)] + grid[cirInd(rep+foo-1, mod)] + grid[cirInd(rep+foo, mod)] + grid[cirInd(rep+foo+1, mod)];
        return neighbors;
     },
     <snip>
  }
4

3 に答える 3

2

#2 と #3 を単純化したビューで見ると、オブジェクト作成の副作用を説明するのに役立つでしょう。

これで明らかになると思います:

alls1=[];
alls2=[];

function inner1(){}
function outer1(){
     if(alls1.indexOf(inner1)===-1){ alls1.push(inner1); }
}


function outer2(){
   function inner2(){}
   if(alls2.indexOf(inner2)===-1){ alls2.push(inner2); }
}

for(i=0;i<10;i++){
   outer1();
   outer2();
}

alert([ alls1.length, alls2.length  ]); // shows: 1, 10

関数はオブジェクトであり、新しいオブジェクトを作成することは決して無料ではありません。

編集:#1対#2の拡張

繰り返しになりますが、単純化された例は次のことを説明するのに役立ちます。

function y(a,b){return a+b;}
var out={y:y};
var ob={
   y:y, 
   x1: function(a){ return this.y(i,a);},
   x2: function(a){ return y(i,a);},
   x3: function(a){ return out.y(i,a);}
}

var mx=999999, times=[], d2,d3,d1=+new Date;
for(var i=0;i<mx;i++){ ob.x1(-i) }
times.push( (d2=+new Date)-d1 );

for(var i=0;i<mx;i++){ ob.x2(-i) }
times.push( (d3=+new Date)-d2 );

for(var i=0;i<mx;i++){ ob.x3(-i) }
times.push( (+new Date)-d3 );

alert(times); // my chrome's typical: [ 1000, 1149, 1151 ]

単純な例ではより多くのノイズがあり、クロージャーは all3 のオーバーヘッドの大きな部分を占めることを理解していますが、それらの間の違いが重要です。

このデモでは、動的システムで観察された大きなゲインは見られませんが、y と out.y のプロファイルが this.y と比較してどれだけ近いかがわかります。他のすべては同じです。

主なポイントは、余分なドット解像度自体が物事を遅くするものではないということです。一部の人がほのめかしているように、特に重要なのは V8 の「this」キーワードです。そうでない場合、out.y() は this.y( )...

Firefox は別の話です。

トレースにより this.whatever を予測できるため、クロムと同じコンプで、3 つのプロファイルすべてが互いに悪いサイコロのロール内にある: [2548, 2532, 2545]...

于 2013-07-11T02:59:54.587 に答える
1

1番に比較的時間がかかる理由は明らかです。オブジェクト スコープ全体にアクセスしてから、プロパティを見つける必要があります。

番号 2 と 3 はどちらも関数へのポインターであるため、シークはありません。

このような状況をテストするための非常に優れたリソースはjsPerfです。そこでシナリオを再作成し、テストを実行して正確な違いと、それらがあなたにとって重要かどうかを確認することを強くお勧めします。

于 2013-07-11T02:43:48.393 に答える
0

OK、私はこの問題をしばらく調査してきましたが、TL;DR - 複雑です。

多くのパフォーマンスの問題は、プラットフォーム、ブラウザー、さらにはマイナーなブラウザーのリビジョン番号に大きく依存していることが判明しました。そして、少しでもありません。jsPerf には、「for vs while」などを示す多くの例があります。または「型付き配列と標準配列」は、ブラウザのリリースが異なると、好ましい実行速度の点で大きく前後します。おそらく、これは JIT 最適化のトレードオフによるものです。

一般的なパフォーマンスの質問に対する簡単な回答 - jsPerf ですべてをテストしてください。このスレッドで得た提案は、すべての場合に役立つものではありませんでした。JITは物事を複雑にします。これは、私のようなバックグラウンドがあり、物事を高速化する傾向がある特定の経験則コーディング パターンを持つ C プログラムに慣れている場合に特に重要です。何も仮定しないでください - ただテストしてください。

注: 元の質問に記載した奇妙な問題の多くは、デフォルトの Chrome プロファイラーを使用しているためです。(例: Ctl+Shift+I メニューから得られるプロファイラー) 非常に高速なループ (グラフィック レンダリングなど) を頻繁に行う場合は、このプロファイラーを使用しないでください。時間分解能は 1 ミリ秒であり、適切なパフォーマンス デバッグを行うには粗すぎます。

実際、ケース 2 が他のケースよりもはるかに高速であるという全体的な問題は、プロファイラーが関数呼び出しの多くを単に「認識」せず、CPU パーセンテージを不適切に報告しているためです。ヒート マップでは、内部ループ関数が起動しているがプロファイラーによって記録されていない巨大なストレッチがはっきりとわかりました。

解決策: http://www.html5rocks.com/en/tutorials/games/abouttracing/# Chrome の about:tracing には、あまり目立たないが、はるかに強力なプロファイラーが組み込まれています。マイクロ秒の解像度、サブ関数の解決のためにコードタグを読み取る機能を備えており、一般的にははるかに優れています。このプロファイラーを使い始めるとすぐに、結果は jsPerf で見たものと一致し、レンダリング時間をほぼ半分に短縮するのに役立ちました。どうやってそれをしたのですか?繰り返しますが、それは単純ではありませんでした。サブルーチンの呼び出しが役立つ場合もあれば、そうでない場合もありました。レンダリング エンジン全体をオブジェクト リテラルからモジュール パターンにリファクタリングすると、少しは効果があるように見えました。for ループ内の乗算演算を前もって計算すると、大きな効果があるように見えました。などなど

about:tracing プロファイラーに関する簡単なメモ: ズームとパンは、キーボードの ASWD で行われます。これを理解するのにしばらく時間がかかりました。また、すべてのタブをプロファイルし、分析中のページ外のタブで動作します。したがって、プロファイラー ビューが乱雑になるため、開いている不要なタブの数を最小限に抑えてください。また、Canvas アプリをテストする場合は、必ずタブをアプリに切り替えてください。タブがアクティブで表示されていない場合、RequestAnimationFrame は通常起動しないためです。

于 2013-07-23T04:36:39.683 に答える