幸い、指摘したように、COMPACT Monoビルドは世代別GCを使用します(WinMo / WinPhone / XBoxのようなフラットリストのみを保持するMicrosoftのGCとはまったく対照的です)。
ゲームが単純な場合、GCはそれをうまく処理する必要がありますが、以下にいくつかのポインタを示します。
時期尚早の最適化
修正する前に、これが実際に問題であることを確認してください。
高価な参照タイプのプーリング
頻繁に作成するか、深い構造を持つ参照タイプをプールする必要があります。それぞれの例は次のとおりです。
- 頻繁に作成:弾丸ゲームの
Bullet
オブジェクト。
- 深い構造:AI実装の決定ツリー。
Stack
プールとしてa を使用する必要があります(aを使用するほとんどの実装とは異なりますQueue
)。この理由はStack
、プールにオブジェクトを返した場合、他のオブジェクトがすぐにそれを取得するためです。アクティブなページにいる可能性がはるかに高くなります-運が良ければCPUキャッシュにもあります。ほんの少し速いだけです。さらに、プールのサイズを常に制限してください(制限を超えている場合は、「チェックイン」を無視してください)。
それらをクリアするための新しいリストの作成を避ける
List
実際に意図したときに新しいものを作成しないでくださいClear()
。バックエンドアレイを再利用して、アレイの割り当てとコピーの負荷を節約できます。これと同様に、意味のある初期容量でリストを作成してみてください(これは制限ではなく、単なる開始容量であることに注意してください)-正確である必要はなく、単なる見積もりである必要があります。これは基本的に、すべてのコレクションタイプに適用する必要がありますLinkedList
。
可能な場合、構造体配列(またはリスト)を使用する
オブジェクト間で構造体を渡すと、構造体(または一般に値型)を使用してもほとんど利点がありません。たとえば、ほとんどの「良い」パーティクルシステムでは、個々のパーティクルは大規模な配列に格納されます。配列とインデックスは、パーティクル自体の代わりに渡されます。これがうまく機能する理由は、GCが配列を収集する必要がある場合、内容を完全にスキップできるためです(プリミティブ配列です-ここでは何もしません)。したがって、GCは1万個のオブジェクトを見る代わりに、1つの配列を見るだけで済みます。繰り返しますが、これは値の型でのみ機能します。
RoyTの後。実行可能で建設的なフィードバックを提供しました。これをさらに拡張する必要があると感じています。この手法は、大量のエンティティ(数千から数万)を扱う場合にのみ使用してください。それに加えて、必要があり、構造体もいけません任意の参照型フィールドを持っており、明示的に型指定された配列に住んでいなければなりません。彼のフィードバックとは反対に、クラスのフィールドである可能性が高い配列に配列を配置しています-つまり、ヒープに到達することを意味します(ヒープの割り当てを回避しようとはしていません-GC作業を回避するだけです)。本当に気になるのは、GCがO(1)
操作ではなく操作で単純に見ることができる多くの値を持つ連続したメモリの塊であるという事実ですO(n)
。
また、これらの配列をアプリケーションの起動のできるだけ近くに割り当てて、フラグメンテーションが発生する可能性、またはGCがこれらのチャンクを移動しようとする際の過剰な作業を軽減する必要があります(組み込みタイプの代わりにハイブリッドリンクリストの使用を検討してください)List
)。
GC.Collect()
これは間違いなくあるBESTする方法足で自分自身を撮影する世代GCで:(「パフォーマンスの考慮事項」を参照します)。極端な量のガベージを作成したときにのみ呼び出す必要があります-それが問題になる可能性のあるインスタンスは、レベルのコンテンツをロードした直後です-それでも、おそらく最初の世代のみを収集する必要があります(GC.Collect(0);
)できれば、オブジェクトを第3世代に昇格させないようにします。
IDisposableとフィールドヌル
オブジェクトが不要になったときにフィールドをnullにすることは価値があります(より制約の多いオブジェクトの場合)。その理由は、GCの動作方法の詳細にあります。現在のコレクションで他のオブジェクトが削除されたためにそのオブジェクトがルート解除された場合でも、ルート化されていない(つまり参照された)オブジェクトのみを削除します(注:これはGCによって異なります使用中のフレーバー-チェーンを実際にクリーンアップするものもあります)。さらに、オブジェクトがコレクションを生き延びた場合、すぐに次の世代にプロモートされます-これは、フィールド内に残っているオブジェクトがコレクション中にプロモートされることを意味します。連続する各世代は、収集するのに指数関数的に高価です(まれに発生します)。
次の例をご覧ください。
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
MyFurtherNestedObject
マルチメガバイトオブジェクトが含まれている場合、GCが長い間そのオブジェクトを参照しないことが保証されます。これは、誤ってG3に昇格したためです。この例と比較してください:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
ディスポーザパターンを使用すると、オブジェクトにプライベートフィールドをクリアするように依頼する予測可能な方法を設定できます。例えば:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}