アレイの最適化
コードで配列を使用するタイミングと方法を最適化 します。
読み終わるまでの所要時間 12 分最終更新 3日前
以下のページでは、配列使用時のコードのパフォーマンスを向上させる例の概要を説明します。
配列をパラメーターとしてメソッドに渡す
場合によっては、新しい配列を作成し、配列に値を入力してからそれを返すメソッドを作成すると便利な場合があります。ただし、このメソッドが繰り返し呼び出されると、そのたびに新しいメモリが割り当てられます。
以下のコード例は、呼び出されるたびに配列を作成するメソッドの例です。
// Bad C# script example: Every time the RandomList method is called it// allocates a new arrayusing UnityEngine;using System.Collections;public class ExampleScript :MonoBehaviour { float[] RandomList(int numElements) { var result = new float[numElements]; for (int i = 0; i < numElements; i++) { result[i] = Random.value; } return result; }}
毎回のメモリ割り当てを回避する 1 つの方法は、配列がリファレンス型であることを利用することです。パラメーターとしてメソッドに渡された配列を変更できます。結果はメソッドが返された後に残ります。これを行うには、サンプルコードを次のように設定します。
// Good C# script example: This version of method is passed an array to fill// with random values.配列をキャッシュして再利用することで、// temporary allocationsusing UnityEngine;using System.Collections;public class ExampleScript :MonoBehaviour { void RandomList(float[] arrayToFill) { for (int i = 0; i < arrayToFill.Length; i++) { arrayToFill[i] = Random.value; } }}
このコードは配列の既存の内容を新しい値に置き換えます。このワークフローフローでは、呼び出し元のコードが配列の初期アロケーションを行う必要がありますが、この機能は呼び出されたときに新しいガベージを生成しません。配列は、次にこのメソッドが呼び出されたときに、マネージヒープで新しい割り当てを行わずに再利用し、ランダムな数値で再入力できます。
配列値を持つUnity APIへの反復的なアクセスを避ける
アレイで意図しない割り当てが発生する原因の1つは、配列を返すUnity APIへの頻繁なアクセスです。配列を返すすべてのUnity APIは、アクセスされるたびに配列の新しいコピーを作成します。コードが配列値 Unity API に必要以上に頻繁にアクセスすると、アプリケーションのパフォーマンスに影響する可能性があります。
例として、以下のコードはループのイテレーションごとに頂点配列の 4 つのコピーを作成します。割り当ては、 プロパティにアクセスするたびに発生します。
.vertices// Bad C# script example: this loop create 4 copies of the vertices array per iterationvoid Update() { for(int i = 0; i < mesh.vertices.Length; i++) { float x, y, z; x = mesh.vertices[i].x; y = mesh.vertices[i].y; z = mesh.vertices[i].z; // ... DoSomething(x, y, z); }}
ループの反復回数に関係なく、このコードを 1 つの配列アロケーションにリファクタリングできます。これを行うには、ループの前に頂点配列をキャプチャするようにコードを設定します。
// Better C# script example: create one copy of the vertices array// and work with thatvoid Update() { var vertices = mesh.vertices; for(int i = 0; i < vertices.Length; i++) { float x, y, z; x = vertices[i].x; y = vertices[i].y; z = vertices[i].z; // ... DoSomething(x, y, z); }}
これを行う最適な方法は、フレーム間でキャッシュされ再利用される頂点を維持し、必要に応じてを使用して頂点にデータを入力することです。
ListMesh.GetVertices// Best C# script example: create one copy of the vertices array// and work with that.リスト<Vector3> m_vertices = new リスト<Vector3>();void Update() { mesh.GetVertices(m_vertices); for(int i = 0; i < m_vertices.Length; i++) { float x, y, z; x = m_vertices[i].x; y = m_vertices[i].y; z = m_vertices[i].z; // ... DoSomething(x, y, z); }}
配列を1回割り当てるプロパティへのアクセスによるCPUパフォーマンスへの影響はそれほど大きくありませんが、タイトなループ内で繰り返しアクセスすると、CPUパフォーマンスのホットスポットが発生します。アクセスが繰り返されると、マネージヒープが拡張されます。
この問題は、モバイル デバイスで頻繁に発生します。 API の動作が前述の例と類似しているためです。また、プロジェクトには以下のようなコードが含まれることもよくあります。コードでは、 プロパティにアクセスするたびにアロケーションが発生します。
Input.touches.touches// Bad C# script example: Input.touches returns an array every time it’s accessedfor ( int i = 0; i < Input.touches.Length; i++ ) { Touch touch = Input.touches[i]; // …}
これを改善するには、ループ状態から配列アロケーションを巻き上げるようにコードを設定します。
// Better C# script example: Input.touches is only accessed once hereTouch[] touchs = 入力.touches;for ( int i = 0; i < touches.Length; i++ ) { Touch touch = touches[i]; // …}
以下のコード例では、前述の例をアロケーション不要の API に変換しています。
Touch// BEST C# script example: Input.touchCount and Input.GetTouch don’t allocate at all.int touchCount = Input.touchCount;for ( int i = 0; i < touchCount; i++ ) { Touch touch = Input.GetTouch(i); // …}
割り当てなしの代替 API
Unity API の中には、メモリ割り当ての原因にならない代替バージョンがあります。可能な場合はこれらを使用する必要があります。以下の表は、割り当て API とその割り当てなしの代替手段の例を示しています。
割り当て API | 非割り当て API 代替 |
|---|---|
| |
| および |
| |
一般に、メソッドが配列を返す場合は、配列を渡すために使用できる割り当てなしバージョンの API があります。
長さゼロの配列の静的インスタンスの使用
開発チームによっては、配列値を持つメソッドが空のセットを返す必要がある場合に、 ではなく空の配列を返すことを好む場合があります。このコーディングパターンは、多くのマネージ言語、特に C# と Java で一般的です。
nullメソッドから長さ 0 の配列を返す場合は、空の配列を繰り返し作成するよりも、事前に割り当てられた長さ 0 の静的インスタンスを返す方が効率的です。
大規模な配列にはネイティブ配列やその他のネイティブコンテナを使用
Unity のガベージコレクターはヒューリスティックガベージコレクターです。つまり、ポインターのサイズを持つすべてのフィールドを、ポインターであるかのように扱います。ガベージコレクターは、遭遇したすべてのポインターとリファレンスをチェックします。
大規模な配列 (要素が 1 万個を超える) を使用する場合、ガベージコレクターは大規模な配列をチェックする必要がある長いポインターのリストとして解釈し、メモリ負荷を増加させ、ガベージコレクターの速度を低下させます。
スクリプト VM は、大きな配列用に大きなマネージマネージヒープスペースを割り当てる必要もあります。これにより、ランダムな値がマネージヒープチャンクのメモリアドレス範囲内の有効なアドレスに見える可能性が高くなります。大規模な配列もマネージヒープ断片化の大きな要因です。
このような長さの配列を割り当てる必要がある場合は、Unity Core APIの名前空間(を含む)でコレクション タイプを使用し、Unity Collectionsパッケージでデータ構造を使用することを検討してください。その他のメリットとして、これらのCollectionsの使用はジョブ システムとBurstと互換性があります。
Unity.CollectionsNativeArray