105

私はかなり複雑な Javascript アプリを持っています。このアプリには、毎秒 60 回呼び出されるメイン ループがあります。多くのガベージ コレクションが行われているようです (Chrome 開発ツールのメモリ タイムラインからの「のこぎり」出力に基づく) - これは、アプリケーションのパフォーマンスに影響を与えることがよくあります。

そこで、ガベージ コレクターの作業量を減らすためのベスト プラクティスを調査しようとしています。(私が Web で見つけた情報のほとんどは、メモリ リークの回避に関するものです。これは少し異なる質問です。メモリが解放されつつあります。ガベージ コレクションが多すぎるためです。)これは主にオブジェクトを可能な限り再利用することに帰着しますが、もちろん悪魔は細部に宿っています。

このアプリは、 John Resig の Simple JavaScript Inheritanceに沿った「クラス」で構成されています。

1 つの問題は、一部の関数が 1 秒間に何千回も呼び出される可能性があること (メイン ループの各反復中に何百回も使用されるため) であり、おそらくこれらの関数のローカル作業変数 (文字列、配列など) です。問題かもしれません。

私はより大きな/より重いオブジェクトのオブジェクトプーリングを認識しています (そして私たちはこれをある程度使用しています) が、特にタイトなループで非常に何度も呼び出される関数に関連して、全面的に適用できるテクニックを探しています.

ガベージ コレクターが実行しなければならない作業量を減らすために、どのような手法を使用できますか?

また、ガベージ コレクションが最も頻繁に行われているオブジェクトを特定するには、どのような手法を使用できるでしょうか。(これは非常に大きなコードベースであるため、ヒープのスナップショットを比較してもあまり効果がありません)

4

4 に答える 4

142

GC チャーンを最小限に抑えるために必要な多くのことは、他のほとんどのシナリオで慣用的な JS と見なされるものに反します。

割り当ては、いくつかの場所で最新のインタープリターで発生します。

  1. またはリテラルnew構文[...]、または{}.
  2. 文字列を連結するとき。
  3. 関数宣言を含むスコープに入るとき。
  4. 例外をトリガーするアクションを実行するとき。
  5. 関数式を評価する場合: (function (...) { ... }).
  6. Object(myNumber)またはのようなオブジェクトに強制する操作を実行すると、Number.prototype.toString.call(42)
  7. のように、内部でこれらのいずれかを実行するビルトインを呼び出すと、Array.prototype.slice.
  8. argumentsパラメータリストを反映するために使用する場合。
  9. 文字列を分割するとき、または正規表現と一致させるとき。

それらを行うことを避け、可能であればオブジェクトをプールして再利用してください。

具体的には、次の機会に注意してください。

  1. クローズド オーバー状態への依存関係がまったくない、またはほとんどない内部関数を、より長く有効なスコープに引き出します。( Closure コンパイラなどの一部のコード縮小機能は、内部関数をインライン化できるため、GC のパフォーマンスが向上する可能性があります。)
  2. 構造化データを表すため、または動的アドレス指定のために文字列を使用することは避けてください。特に、splitまたは正規表現の一致を使用して繰り返し解析することは避けてください。それぞれに複数のオブジェクトの割り当てが必要になるためです。これは、ルックアップ テーブルと動的 DOM ノード ID へのキーで頻繁に発生します。たとえば、lookupTable['foo-' + x]両方document.getElementById('foo-' + x)とも文字列の連結があるため、割り当てが必要です。多くの場合、再連結する代わりに、有効期間の長いオブジェクトにキーをアタッチできます。サポートする必要があるブラウザーによっては、Mapオブジェクトをキーとして直接使用できる場合があります。
  3. 通常のコード パスで例外をキャッチしないようにします。の代わりにtry { op(x) } catch (e) { ... }、してくださいif (!opCouldFailOn(x)) { op(x); } else { ... }
  4. JSON.stringifyメッセージをサーバーに渡す場合など、文字列の作成を避けられない場合は、複数のオブジェクトを割り当てる代わりに、内部のネイティブ バッファーを使用してコンテンツを蓄積する組み込みの likeを使用します。
  5. 頻度の高いイベントにコールバックを使用することは避け、可能な場合は、メッセージの内容から状態を再作成する長寿命の関数 (1 を参照) をコールバックとして渡します。
  6. arguments呼び出されたときに配列のようなオブジェクトを作成する必要があるので、それを使用する関数は使用しないでください。

JSON.stringify送信ネットワーク メッセージの作成に使用することを提案しました。を使用して入力メッセージを解析するJSON.parseには、明らかに割り当てが必要であり、大きなメッセージには多くの割り当てが必要です。着信メッセージをプリミティブの配列として表すことができれば、多くの割り当てを節約できます。割り当てを行わないパーサーを構築できる他の組み込み関数はString.prototype.charCodeAt. それを使用するだけの複雑な形式のパーサーは、読むのが地獄になるでしょう。

于 2013-08-23T20:25:37.217 に答える
13

Chrome 開発者ツールには、メモリ割り当てを追跡するための非常に優れた機能があります。それはメモリータイムラインと呼ばれます。 この記事では、いくつかの詳細について説明します。これが「のこぎり歯」について話していることだと思いますか?これは、ほとんどの GC されたランタイムの通常の動作です。割り当ては、使用量のしきい値に達してコレクションがトリガーされるまで続行されます。通常、さまざまなしきい値でさまざまな種類のコレクションがあります。

Chrome のメモリ タイムライン

ガベージ コレクションは、トレースに関連付けられたイベント リストに、その期間と共に含まれます。私のかなり古いノートブックでは、エフェメラル コレクションは約 4Mb で発生し、30 ミリ秒かかります。これは、2 つの 60Hz ループ反復です。これがアニメーションの場合、30 ミリ秒のコレクションがスタッターを引き起こしている可能性があります。ここから始めて、環境で何が起こっているかを確認する必要があります。コレクションのしきい値はどこにあり、コレクションにかかる時間はどれくらいですか。これにより、最適化を評価するための基準点が得られます。しかし、おそらく、割り当て速度を遅くしてコレクション間の間隔を長くすることによってスタッターの頻度を減らすよりも良いことはありません。

次のステップは、プロファイルを使用することです。レコード タイプごとの割り当てのカタログを生成するレコード ヒープ割り当て機能。これにより、トレース期間中にどのオブジェクト タイプが最も多くのメモリを消費しているかがすぐにわかります。これは、割り当て率に相当します。レートの高い順にこれらに注目してください。

技術はロケット科学ではありません。ボックス化されていないオブジェクトでできる場合は、ボックス化されたオブジェクトを避けます。グローバル変数を使用して、反復ごとに新しいオブジェクトを割り当てるのではなく、単一のボックス化されたオブジェクトを保持して再利用します。一般的なオブジェクト タイプを放棄するのではなく、フリー リストにプールします。将来の反復で再利用できる可能性が高い文字列連結結果をキャッシュします。代わりに、外側のスコープに変数を設定して、関数の結果を返すためだけに割り当てを回避します。最適な戦略を見つけるには、それぞれのオブジェクト タイプを独自のコンテキストで検討する必要があります。詳細についてサポートが必要な場合は、検討中の課題の詳細を説明する編集を投稿してください。

ガベージの生成を減らすために、アプリケーション全体で通常のコーディング スタイルを変更しないことをお勧めします。これは、時期尚早に速度を最適化してはならないのと同じ理由です。あなたの努力のほとんどと、追加された複雑さとコードの不明瞭さの多くは無意味になります。

于 2013-08-30T03:11:29.223 に答える
9

一般的な原則として、できるだけ多くをキャッシュし、ループの実行ごとに作成と破棄を最小限に抑えたいと考えています。

私の頭に浮かんだ最初のことは、メイン ループ内での無名関数 (ある場合) の使用を減らすことです。また、他の関数に渡されるオブジェクトを作成および破棄するという罠に陥りやすいでしょう。私は決して JavaScript の専門家ではありませんが、次のように想像できます。

var options = {var1: value1, var2: value2, ChangingVariable: value3};
function loopfunc()
{
    //do something
}

while(true)
{
    $.each(listofthings, loopfunc);

    options.ChangingVariable = newvalue;
    someOtherFunction(options);
}

これよりもはるかに高速に実行されます。

while(true)
{
    $.each(listofthings, function(){
        //do something on the list
    });

    someOtherFunction({
        var1: value1,
        var2: value2,
        ChangingVariable: newvalue
    });
}

プログラムのダウンタイムはありますか? 1 ~ 2 秒 (アニメーションなど) スムーズに実行する必要があり、処理に時間がかかることはありませんか? この場合、通常はアニメーション全体でガベージ コレクションされるオブジェクトを取得し、グローバル オブジェクトでそれらへの参照を保持していることがわかります。次に、アニメーションが終了したら、すべての参照をクリアして、ガベージ コレクターに任せることができます。

あなたがすでに試して考えたことと比べて、これがすべて少し些細なことである場合は申し訳ありません.

于 2013-08-21T18:12:34.450 に答える