CPUコードの最適化

ゲームがスムーズなフレームレートで実行されるようにするには、CPUとGPUがフレーム内の「タスク」をタイムリーに完了できるようにする必要があります。この記事で説明するこれらのC#コードのパフォーマンスは、CPU自体の各フレームの時間に影響します。ゲームがフレームレートの低下、断続的なフリーズ、さらにはフリーズに悩まされている場合、これが開発者自身によって作成されたスクリプトのパフォーマンスの低下が原因であるかどうかを考慮する必要があります。

 

パフォーマンスの最適化問題は、実際には「時空間問題」です。操作に必要な時間をできるだけ節約し、メモリが占​​めるスペースを節約したいと考えています。同じことがC#コードの最適化にも当てはまり、一方ではCPU時間の最適化であり、他方ではメモリ割り当ての最適化です。また、「スペースの問題」と「時間の問題」は相互に変換されることがよくあります。メモリを最適化する目的の1つは、GCを削減することです。これは、CPU時間の削減に起因する可能性があります。この記事で説明する一連のルールは、主にCPU時間のルールを対象としています。


 

1.クラスにOnGUIメソッドがある

 

ルールに含まれるOnGUIは、UnityのIMGUIシステムがUIを描画するために呼び出すメソッドです。このメソッドがMonobehaviorを継承するスクリプトで記述されている場合、Unityはフレームごとに自動的に呼び出します。

UI描画にIMGUIを使用して、何かを変更したい場合は、GUI全体を再描画する必要があり、OnGUIはフレーム内で複数回呼び出されるため、CPU時間が増加します。さらに、OnGUI関数が不適切に使用されると、monoメモリの継続的な割り当てが発生しやすくなります。したがって、ゲームプロジェクトでは、IMGUIは一般的にUI開発には使用されません(UGUI、NGUIなどが一般的に使用されます)。 IMGUIは通常、エディター拡張機能の開発、ゲームのデバッグパネルの描画などに使用されます。

 

2.クラスには空のUpdate、LateUpdate、FixedUpdateメソッドがある

 

上記のルールで「Monobehavior」について簡単に説明しました。Unityのスクリプトは、実際にはデフォルトでこのMonobehaviorを継承しています。 Update、LateUpdate、FixedUpdateは、Monobehaviourクラスの「Messages」に属します。これらは、Monobehaviourクラスから継承されませんが、Monobehaviourクラスのスクリプトで有効になります。これらのメソッドがスクリプトで記述されている場合、対応するスクリプトがシーンに配置され、enableがtrueの場合、ゲームの実行中にフレームごとに呼び出されます。

これらのメソッドが空の場合でも、次の2つの主な理由により、実行時に呼び出されるため、CPU時間の消費が発生します。

 

1、これらのメソッドはNativeレイヤーからマネージドレイヤーへの呼び出しであり、C ++とC#の間の通信には一定の消費があります。

2、これらのメソッドが呼び出されると、Unityは一連のセキュリティチェック(GameObjectが破棄されていないことの確認など)を実行するため、CPU時間が消費されます。

 

3.当クラスのメソッドにCamera.mainへのコールがある

Camera.mainは、実際にはGetメソッドを実装するプロパティです。呼び出されるたびに、タグ「MainCamera」が付いたシーン内の最初のカメラを探して返します。古いバージョンのUnityを使用してCamera.mainを呼び出すには、すべてのタグGameObjectをトラバースし、タグ比較を実行し、カメラコンポーネントを見つける必要があります。これには時間がかかり、エンジンは結果を自動的にキャッシュしません。

ただし、Camera.mainはUnity 2020.2バージョンで最適化されており、CPU時間の消費が高くなることを回避しています。2020.2以降のUnityバージョンを使用しているチームは、このルールを無視できます。

 

4.当クラスのメソッドにComputeBuffer.GetDataのコールがある

 

ComputeBuffer.GetDataは、GPUのBufferから対応する計算結果を読み取り、対応する配列に入力します。プロセス全体が同期操作であるため、呼び出しが行われると、GPUがデータを返すまで呼び出しスレッドがブロックされます。

データの量が多い場合、ComputeBuffer.GetDataはCPU時間と対応するMonoメモリスペースの大部分を消費します。他の非同期操作を試して、同じ値の効果を得ることができます。

 

5.当クラスのメソッドにテクスチャSetPixelsへのコールがある

 

SetPixelsを使用して、テクスチャ固有のmipmapレイヤーのピクセルを変更できます。これにより、1グループの配列ピクセル値をスタンプの指定mipmapレイヤーに割り当てられます。Apply()を呼び出した後、ピクセルはグラフィックカードに転送されます。 Color32はColorタイプよりも占有するスペースが少ないため、SetPixels32を使用するとSetPixelsよりもCPU時間が短くなることに注意してください。したがって、効果が許せば、SetPixels()の代わりにSetPixels32()メソッドを使用することをお勧めします。

 

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

 

GameObject.SendMessageは、対応するGameObjectのスクリプトで名前が指定された関数を呼び出すために使用されます。この関数は、GameObjectのすべてのコンポーネントと、コンポーネントスクリプトのすべての関数をトラバースするため、CPUの消費が高くなります。したがって、開発者は不要なSendMessageの使用を減らす必要があります。

 

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

 

GetComponentsInChildrenは、現在のGameObjectとその子ノードの特定のタイプのすべてのコンポーネントを取得するために使用され、すべての適格なオブジェクトを含む配列を返します。

GetComponentsInParentは、現在のオブジェクトとその親ノードのコンポーネントの特定のタイプすべてを取得するために使用されます。もちろん、ここでは、コンポーネントの隠れた状況についての説明は無視します。

以上の両者の使用には大規模な検索トラバーサルが含まれ、CPUの大規模な計算アセットが占有されるため、開発チームは関連する呼び出しを可能な限り減らすように努める必要があります。Updateのような頻繁に呼び出される関数に表示されないように、コールの結果をキャッシュすることを試すことができます。また、これら2つの関数については、呼び出しごとにMonoメモリが割り当てられないように、開発チームがList型の参照をパラメータとして受け入れるバージョンを使用することをお勧めします。

 

8.当クラスのメソッドにFindObjectsOfTypeのコールがある

 

FindObjectsOfTypeを使用すると、シーン内のGameObjectとComponentをトラバースし、ターゲットTypeと同じタイプのコンポーネントを配列として返します。「Find」クラスの操作は、小さなプロジェクトでは大きな影響を与えない場合がありますが、プロジェクトのボリュームが増加し、シーン内のオブジェクトの数が増えると、この操作によって発生するCPU時間の消費も無視できなくなります。そして、この関数はMonoメモリの割り当てを引き起こします。したがって、このような関数呼び出しはできるだけ避けるか、プロジェクトのパフォーマンスへの影響を減らすために、一度呼び出して結果をキャッシュすることをお勧めします。

 

9.当クラスのメソッドにはReflection関連関数のコールがある

 

リフレクション(Reflection)は、実行時にコードをバインドするために使用される技術です。コードが取得する必要のあるタイプ、呼び出す関数などのような情報が実行時にしか決定できない場合は、リフレクションが必要です。

ただし、ランタイムバインディングは、パフォーマンスの消費が高くなることを意味します。プロジェクトがリフレクション関連のメソッドを呼び出す場合、タイプや関数などの情報を取得し、パラメーターの検証、エラー処理、セキュリティチェックを実行する必要があります。これにより、対応するCPU計算の消費が高くなり、Monoメモリの割り当てが発生しやすくなります。したがって、ゲームプロジェクトでは、リフレクションの使用をできるだけ避けることをお勧めします。

 

10.当クラスのメソッドにはRendererのMaterial/Materialsの取得がある

 

Unityでは、Rendererタイプで.materialと.materialsを呼び出すと、Unityは新しい材質球インスタンスを生成します。その主な効果は次のとおりです。

.materialを使用し、マテリアルインスタンスを作成し、プロパティを変更してさまざまなレンダリング効果を実現すると、時間の消費が高くなります。ここ「MaterialPropertyBlockを使用したMaterialプロパティ操作の置き換え」を参照してください。

同じShaderが使用されますが、MaterialインスタンスのGameObjectが異なるため、バッチ処理を実行できず、かえってDraw Callsが増加し、CPU時間が増加します。

 

.materialが新しいGameObjectのRendererに呼び出されるたびに、新しいMaterialインスタンスが生成され、GameObjectが破棄された後、Materialインスタンスを自動的に破棄できないため、アセット管理に一定のコストが発生します。それを処理するには、手動で「UnloadUnusedAssets」をコールしてアンロードする必要がありますが、これによりパフォーマンスの消費が発生します。管理が不十分だと、多くの冗長性や材質球のリークが発生し、極端な場合はメモリが過剰になる可能性があります。

 

アクティブなMaterialPropertyBlockを介してマテリアルのプロパティを変更するか、限られた数のマテリアルインスタンスを手動で管理することをお勧めします。同じ効果を持つオブジェクトは、sharedMaterialを介してマテリアルインスタンスを共有します。


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

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

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