Unity Engine #1 メモリ管理 1
このノートは主に Unity エンジンのメモリ管理についてです。
基本概念
Unity の**マネージドメモリシステム(Managed Memory System)**は、Mono または IL2CPP 仮想マシン(VM)に基づく C# スクリプティング環境です。マネージドメモリシステムの利点は、メモリの解放を管理するため、コードを通じて手動でメモリの解放を要求する必要がないことです。
マネージドメモリ:.NET のガベージコレクタ(GC とよく呼ばれる)によって管理され、主に C# オブジェクトの格納に使用されます。
アンマネージドメモリ:Unity エンジンとプラグインが使用するメモリ。Texture2D、Mesh などのネイティブリソースの格納に使用されます。
マネージドヒープ:.NET ランタイム環境で C# オブジェクトを格納するために使用されるメモリ領域です。Unity では、クラスのインスタンス、配列、文字列など、スクリプトを通じて作成されるほぼすべてのオブジェクトが含まれます。ここにあるものは GC によって自動的に管理されることに注意してください。コンピュータシステムと一致し、異なるデータ型はマネージドヒープを占有する際に異なるサイズを占有します。
自動メモリ管理
Unity では、一般的な C とは異なり、メモリヒープの占有に malloc 関数を使用する必要はなく、もう使用されないメモリブロックに対して free 関数を実行する必要もありません。Unity のスクリプティングバックエンドは、ガベージコレクタを使用してアプリケーションのメモリを自動的に管理します。自動メモリ管理は、明示的な割り当て/解放よりもコーディングの労力が少なく、メモリリークの可能性を減らします。
メモリフラグメンテーション
異なるデータ型は異なるサイズを持つため、大きなメモリブロック M を占有する必要がある場合、一部の占有メモリを解放した後に現在利用可能なメモリの合計が M より大きくても、M をメモリに収めることができない場合があります。これは、解放されたメモリがフラグメント化しているためです。
大きなオブジェクトが割り当てられ、それを収容するのに十分な連続した空きスペースがない場合、上図のように、Unity のメモリマネージャーは2つの操作を実行します:
- まず、GC が実行される(まだ実行されていない場合)。割り当て要求を満たすために十分なスペースを解放しようとする。
- GC が実行された後も、要求されたメモリ量を収容するのに十分な連続スペースがない場合、ヒープを拡張する必要がある。ヒープ拡張の正確な量はプラットフォームによって異なる。ただし、ほとんどのプラットフォームでは、ヒープが拡張されるとき、以前の拡張量の2倍の量で拡張される。
しかし、ヒープ拡張はリスクの高い操作であるため、Unity のガベージコレクション戦略はメモリをより頻繁にフラグメント化する傾向があります。さらに、頻繁な GC 操作はゲームのフリーズを引き起こす可能性があるため、メモリ割り当てを最小限に抑え、Update などの頻繁に呼ばれるメソッドで新しいオブジェクトを作成することを避けるべきです。
オブジェクトプール
**オブジェクトプーリング(Object Pooling)**は、必要なときに破棄して作成する代わりに、事前にインスタンス化されたオブジェクトのコレクションを再利用・管理するためのデザインパターンです。Unity での頻繁な作成と破棄は、パフォーマンスの低下とメモリ割り当ての問題につながります。GC 呼び出しの回数を減らすことで、ゲームのパフォーマンスを向上させることもできます。
使用シナリオ:弾丸、パーティクルエフェクト、敵など、頻繁に作成・破棄されるオブジェクトに適しています。リアルタイムストラテジーゲームやシューティングゲームなど、高性能でスムーズな体験が必要なゲームでは特に重要です。
実装手順
- オブジェクトプールのシングルトンを作成する。
- ゲーム開始時(Start または Awake)に、必要に応じて一定数のオブジェクトを事前にインスタンス化する。
- プールからオブジェクトを取得・返却するメソッドを提供する。
注意:
GC 呼び出しを減らすが、継続的に占有されるメモリも増加させる。
アンマネージドリソース
アンマネージドリソースは、Unity エンジンによって直接管理され、.NET ガベージコレクタ(GC)によって制御されないリソースです。テクスチャ、オーディオファイル、3D モデル、アニメーションなどが含まれます。Asset Bundles または Resources クラスを通じてリソースをロードし、Destroy 関数を通じてリソースを解放して、以下で言及するメモリリークを引き起こさないようにします。
Resources クラスの使用
Assets に Resources という名前のフォルダを作成します。その後、C# スクリプトで、
Plain Textvar prefab = Resources.Load<GameObject>("MyPrefab");
を渡して必要なアンマネージドリソースを呼び出せます。
Resources.UnloadAsset(AssetName) を通じてリソースを解放することもできます。Resources.UnloadUnusedAssets() を使用して、現在使用されていないすべてのリソースを解放します。
メモリリーク
もう使用されていないメモリが GC によって回収されないと、メモリリークが発生します。以下はいくつかの可能性のあるメモリリークです:
アンマネージドリソースが解放されない
大量のアンマネージドリソース(Texture、Audio Clip、Mesh など)が作成され、不要になったときに解放されない。
解決策:Destroy メソッドを使用して解放する。または .Dispose() を呼び出す。
静的変数とシングルトン
静的変数またはシングルトンクラスがシーン内のオブジェクトを参照しており、シーンが切り替わってもこれらのオブジェクトは破棄されない。
対策:不要になったら静的変数を null に設定することを確認するか、シングルトンパターンを慎重に使用する。
イベントとデリゲート
イベントに登録したオブジェクトが、破棄される前に登録解除しなかったため、イベントがこれらのオブジェクトへの参照を保持し続ける。
対策:OnDestroy() メソッドで、すべてのイベントから登録解除することを確認する。
リソースの動的ロード
Resources.Load を使用してリソースを動的にロードしたが、正しくアンロードしていない。これらのリソースが常にメモリに残る原因となる。
解決策:Resources.UnloadUnusedAssets を適切に使用して、もう使用されていないリソースをアンロードする。
メモリ割り当てを減らすためのコード最適化
文字列操作の最適化
string 型については、「+」演算をできるだけ減らす。これにより多くの一時的な string オブジェクトが作成されるため。
解決策:特にループ内で文字列を連結する場合は、「StringBuilder」を使用して文字列を構築する。
ボクシングとアンボクシングを避ける
Unity では、値型(int、float、bool など)と参照型(Object など)の区別がある。
値型と参照型の間の変換はボクシングとアンボクシングと呼ばれ、追加のメモリ割り当てにつながる可能性があります。不要なボクシングとアンボクシング操作を避け、データ型の使用を明確にするように努めるべきです。
データ構造の最適化
まず、多くのデータ構造の長所と短所を明確にする必要があります。
各データ構造の長所、短所、複雑さを記憶した上で、必要な操作を確保することを前提に、メモリ占有が少なく操作が速いデータ型を選択するように努めるべきです。
参考文献:
- Unity 3D メモリ管理、https://www.mvrlink.com/unity3d-managed-memory/
- オブジェクトプール、https://blog.csdn.net/l773575310/article/details/71601460
- Unity メモリリーク、https://wetest.qq.com/labs/150
