Unityでゲームを構築するとき、GCをどのように考慮する必要がありますか?


15

*私の知る限り、Unity3D for iOSはMonoランタイムに基づいており、Monoには世代別のマーク&スイープGCしかありません。

このGCシステムは、ゲームシステムを停止するGC時間を避けられません。インスタンスプーリングはこれを減らすことができますが、完全にではありません。CLRの基本クラスライブラリでインスタンス化を制御できないためです。これらの隠された小さくて頻繁なインスタンスは、最終的に不確定なGC時間を発生させます。完全なGCを定期的に強制すると、パフォーマンスが大幅に低下します(実際、Monoは完全なGCを強制できますか?)

では、パフォーマンスを大幅に低下させずにUnity3Dを使用する場合、このGC時間をどのように回避できますか?


3
最新のUnity Pro Profilerには、特定の関数呼び出しで生成されるガベージの量が含まれています。このツールを使用すると、ガベージを生成している可能性のある他の場所を確認することができます。
テトラッド

回答:


18

幸い、指摘したように、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;
    }
}

1
WTFについてはどうですか?Windows上の.NET Frameworkは絶対世代です。クラスにはGetGenerationメソッドもあります。
DeadMG

2
Windows上の.NETは世代別ガベージコレクターを使用しますが、WP7およびXbox360で使用される.NET Compact Frameworkはより制限されたGCを持ちますが、おそらく完全な世代別リストではありません。
ロイT.

Jonathan Dickinson:構造体が常に答えとは限らないことを付け加えたいと思います。.NetおよびおそらくMonoでは、構造体は必ずしもスタック上に存在するわけではありません(良い)が、ヒープ上に存在することもできます(ガベージコレクターが必要な場所です)。このためのルールはかなり複雑ですが、一般に、構造体に値以外の型が含まれている場合に発生します。
ロイT.

1
@RoyT。上記のコメントを参照してください。構造体は、パーティクルシステムのように、配列内の大量のデータに適していることを示しました。配列内のGC構造体が心配な場合は、次の方法があります。粒子のようなデータ用。
ジョナサンディキンソン

10
@DeadMGもWTFを使用することはほとんどありません-実際に答えを正しく読んでいないことを考慮してください。このような一般的な行儀の良いコミュニティで嫌なコメントを書く前に、気性を落ち着かせて答えを読み直してください。
ジョナサンディキンソン

7

ベースライブラリ内では、それを必要とするものを呼び出さない限り、ほとんど動的にインスタンス化されません。メモリの割り当てと割り当て解除の大部分は、独自のコードから行われ、それを制御できます。

私はそれを理解しているので、これを完全に回避することはできません-できる限りオブジェクトをリサイクルしてプールし、クラスの代わりに構造体を使用し、UnityGUIシステムを回避し、一般的に動的メモリ内に新しいオブジェクトを作成しないようにするだけですパフォーマンスが重要な場合。

また、特定の時間にガベージコレクションを強制することもできますが、これは役立つ場合もあれば、役に立たない場合もあります。http//unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.htmlのいくつかの提案を参照してください。


2
GCを強制することの意味を本当に説明する必要があります。GCのヒューリスティックとレイアウトに大混乱を招き、誤って使用するとメモリの問題が大きくなる可能性があります。XNA/ MVP /スタッフコミュニティは一般に、ポストロードコンテンツが収集を強制する唯一の妥当な時間であると見なします。問題に1つの文だけを捧げる場合、完全に言及することは本当に避けてください。GC.Collect()=現在メモリを解放していますが、後で問題が発生する可能性が非常に高くなります。
ジョナサンディキンソン

いいえ、GCを強制することの意味を説明する必要があります。それは、私よりもGCについてよく知っているからです。:)ただし、Mono GCはMicrosoft GCと必ずしも同じではなく、リスクも同じではない可能性があることに注意してください。
キロタン

それでも、+ 1。私は先に進んで、GCでの個人的な経験について詳しく説明しました-あなたは面白いと思うかもしれません。
ジョナサンディキンソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.