C#コードの最適化:Monoメモリの節約

C#は仮想マシン(VM)環境(Mono仮想マシンまたはIL2CPP仮想マシン)で実行され、割り当てられたMonoメモリは仮想マシンによって管理および再利用されるため、C#コードは「マネージドコード」と呼ばれ、Monoメモリも「マネージドメモリ」として知られています。「マネージドメモリ」の解放は、仮想マシンのGC(ガベージコレクション)メカニズムに依存しますが、この記事では説明していません。

 

C#プログラム開発で従うべき基本原則は、不要なMonoメモリの割り当てを回避することであり、Monoメモリの割り当ては主に次の結果を引き起こします。

 

プログラムが使用するメモリの総量が多すぎます。メモリは限られたアセットです。ゲーム(特にモバイルゲーム)の場合、メモリ使用量は貴重なものであり、解放できないメモリが多すぎると、プログラムがクラッシュする可能性があります。

割り当ての回数が多すぎると、Monoの断片化が増加し、必要な連続メモリを割り当てることができなくなり、プログラムがクラッシュする可能性があります。

メモリ割り当てはGC(ガベージコレクション)をトリガーし、GCは高価であり、遅延を引き起こします。


 

1.当クラスのメソッドには.tagへのコールがある

tagはシーン内のGameObjectのタグであり、クラスGameObjectのメンバーtagは属性です。属性を取得するとき、基本的にget_tag()関数をコールし、nativeレイヤーから文字列を返します。文字列は参照型であり、この文字列が返されると、Monoメモリが割り当てられます。ただし、Unityエンジンはキャッシュによってget_tagを最適化しません。get_tagがコールされるたびに、Monoメモリが再び割り当てされます。

 

したがって、tagを比較する必要がある場合は、関数GameObject.CompareTag()を使用することをお勧めします。この関数は、nativeレイヤーに実装され、マネージMonoメモリの割り当てを引き起こさないため、GCの負荷を回避できます。

 

⒉当クラスのメソッドには、texture GetPixels()/ GetPixels32()のコールがある

 

Texture2DタイプのオブジェクトでGetPixels()およびGetPixels32()をコールすることは、通常、指定されたMipmapレイヤーのすべてのピクセル情報を取得することであり、画像上のピクセル数は非常に多いことがよくあります。

 

メモリ割り当てに関しては、この関数はマネージMonoにメモリを割り当てて、テクスチャデータのピクセル情報を格納しますが、エンジンはそれをキャッシュしません。したがって、頻繁にコールされる関数で使用すると、永続的なMonoメモリ割り当てが発生します。

 

時間の面では、大規模な並列演算が得意なGPUが画像情報を処理するのは非常に簡単ですが、ピクセルごとの情報を取得する場合、CPUは少し面倒です。また、GetPixels()はCPUによって同期的に実装されるため、時間がかかり、コールのスレッドが同時にブロックされ、ラグが発生する可能性があります。したがって、必要な場合を除いて、GetPixels()を使用することはお勧めしません。

 

3.当クラスのメソッドにGetComponentsInChildrenコールがある/当クラスのメソッドにGetComponentsInParentコールがある

 

前回の記事「C#コードの最適化:CPU時間の節約」では、GetComponentsInChildrenとGetComponentsInParentについて簡単に説明しました。ここでは、実際のコールでは、オブジェクトのトラバーサルと結果の戻りが含まれるため、不適切に使用すると、永続的なMonoメモリの割り当てが発生することをさらに説明します。開発チームは、List型の参照をパラメータとして受け入れるバージョンを使用することをお勧めします。これにより、コールごとにMonoメモリを割り当てないようにすることができます。

 

4.当クラスのメソッドにLinq関連関数へのコールがある

 

Linq関連の関数は、通常、データのクエリと処理に使用されます。機能的に言​​えば、一連のデータに対してさまざまなif判定とforループ処理を実行することです。 Linqが提供するAPIを使用すると、集合体データを処理するSQLステートメントスタイルのコードを記述できます。これにより、コードの単純さと読みやすさが大幅に向上し、保守も便利になり、書き込み効率が向上しますが、これらの利点はパフォーマンスが犠牲になります。

 

Linqは実行中にいくつかの一時変数を生成し、デリゲート(lambda式)を使用します。デリゲートを条件付き判定方法として使用すると、時間のコストが高くなり、一定量のMonoメモリが割り当てられます。したがって、一般的なUnityゲームプロジェクトの開発では、Linq関連の関数を使用することはお勧めしません。エディター機能の開発では、LinqとReflectionを一緒に使用することがよくあります。

 

5.当クラスのメソッドに、RendererにsharedMaterialsの取得がある

 

同様に、前回の記事「C#コードの最適化:CPU時間の節約」では、.material / materialsについて説明しました。簡単に言うと、.material(s)をコールすると、新しいシェーダーインスタンスが生成されます。sharedMaterialsは共有マテリアルであり、新しいマテリアルインスタンスは生成されません。ただし、.sharedMaterialsをコールすると、Monoメモリが割り当てられます。コールごとに、Materialのインデックスを保持する配列が割り当てられます。このアレイのメモリ使用量は比較的小さいですが、頻繁にコールすることはお勧めしません。

 

6.当クラスのメソッドにInput.touchesコールがある

 

モバイルプロジェクトの相互作用では、タッチ操作は非常に頻繁で一般的であると言えます。タッチ操作の取得では、Input.touchesを使用して、現在のフレーム内のすべてのタッチ操作のステータスと対応するデータを取得します。しかし、.touchesの実装を見ると、コールたびにtouchesの配列がnewになり、ある程度のMonoメモリが割り当てられることがわかります。したがって、開発チームは、追加のMonoメモリの使用を防ぐために、Input.touchesを頻繁に使用しないようにする必要があります。

 

7.FindObjectsOfTypeコール

 

前回の記事「C#コードの最適化:CPU時間の節約」では、FindObjectsOfTypeについて簡単に紹介しました。 CPU時間は増加する上に、Monoメモリ割り当てのかなりの部分を占めるため、パフォーマンスへの影響を軽減するために、1回のコールで結果をキャッシュすることをお勧めします。

 

8.当クラスのメソッドにTextAsset / WWW.bytesコールがある

 

このルールは、実際には2つの無関係なUnityAPI用です。 1つ目は、TextAssetのbytesプロパティです。 TextAssetはUnityのテキストアセットであり、txt、html、bytes、csvなどのさまざまな形式のファイルの変換をサポートしています。 bytesプロパティを取得するとき、Unityはnativeレイヤーからバイト配列(byte [])を取得し、それによって一定量のMonoメモリを割り当てます。

 

別のAPIは、WWWクラスのbytesメンバーを参照します。呼び出されるたびに、Monoメモリの割り当てが発生します。UnityがWWWクラスの関連するインターフェイスを放棄したため、UnityWebRequestを使用して関連機能を実現することをお勧めします。


UWA公式サイト:https://jp.uwa4d.com

UWA公式ブログ:https://blog.jp.uwa4d.com

UWA公式Q&Aコミュニティ(中国語注意):https://answer.uwa4d.com