パフォーマンスの問題に様々な原因があるように、コードの最適化方法も様々です。通常、 CPU の最適化を行うに当たっては、事前にアプリケーションの綿密なプロファイリングを行うことが強く推奨されます。ただし、どんな場合にも適用できる CPU 最適化もいくつかあります。
Unity は、アニメーター、マテリアル、シェーダーのプロパティを内部的に表す際、文字列による名は使用しません。速度を高めるために、全てのプロパティ名はプロパティ ID 内でハッシュ化され、プロパティを示すにはこの ID が使用されます。
したがって、Set や Get メソッドをアニメーター、マテリアル、シェーダーなどに使用する場合は、string 型ではなく int 型のメソッドを使用してください。string 型メソッドは単純に、文字列をハッシュにした ID を int 型メソッドに転送します。
文字列のハッシュから作成されたプロパティ ID は、1回の実行中の間において有効です。この使用方法として最も簡単なのは、静的な読み込み専用の int 変数を各プロパティ名について宣言し、その int 変数を文字列の代わりに使用することです。これは、起動時に、追加の初期化コードなしに自動的に初期化されます。
アニメーターのプロパティ名に適した API は Animator.StringToHash、マテリアルとシェーダーのプロパティ名に適した API は Shader.PropertyToID です。
Unity 5.3 以降は、Physics クエリ API の割り当てなしのバージョンが提供されています。例えば RaycastAll コールは RaycastNonAlloc に置き換え、 SphereCastAll コールは SphereCastNonAlloc に置き換えることができます。 2D アプリケーションの場合は、全ての Physics2D クエリ API の割り当てなしのバージョンもあります。
Mono と IL2CPP ランタイムは、UnityEngine.Object から派生したクラスのインスタンスを特定の方法で処理します。インスタンス上でメソッドを呼び出すと、実際にはエンジンコードを呼び出します。エンジンコードは、スクリプト参照をネイティブ参照に変換するためのルックアップと検証を実行する必要があります。小さいながらも、この型の変数を null と比較するコストは、純粋に C# クラスと比較するよりもはるかに高負荷です。このため、タイトなループやフレームごとに実行されるコードでは、これらの null 比較を避けてください。
タイトなループ内にあるベクターやクォータニオンの計算に関しては、「整数演算のほうが浮動小数点演算よりも速く、浮動小数点演算のほうがベクターやマトリックス演算やクォータニオン計算よりも速い」ということを覚えておきましょう。
このため、可換演算あるいは結合演算の可能な範囲では常に、 個々の計算処理のコストを最小限に抑えるようにしてください。
Vector3 x;
int a, b;
// 非効率的(ベクターの乗算が 2 回発生する)
Vector3 slow = a * x * b;
// 効率的(整数の乗算が 1 回、 ベクターの乗算が 1 回)
Vector3 fast = a * b * x;
HTML の文字列方式 (#RRGGBBAA
) と Unity ネイティブの Color
や Color32
方式との間で色指定の方式の変換を行わなければならないアプリケーションでは、 Unity Community のスクリプトが一般的に使用されています。このスクリプトは速度が遅く、また文字列操作のために相当な量のメモリ割り当てを発生させるものでした。
Unity 5 以降は、この変換を効率的に実行できる ColorUtility API がビルトインで提供されています。可能な限り、こちらの API を使用してください。
基本的には、本番用のコードでは GameObject.Find
と Object.FindObjectOfType
を一切使用しないことが推奨されます。これらの API を使用すると、Unity はメモリ内の全てのゲームオブジェクトとコンポーネントの反復処理を行わなければならず、プロジェクトの規模が少し拡大すると効率が大幅に低下します。
シングルトンオブジェクトのアクセサーの場合はこの限りではありません。グローバル マネージャーオブジェクトは instance プロパティをアクセス可能にすることがよくあり、シングルトンの既存のインスタンスを検知するためにゲッター関数内に FindObjectOfType
コールを持っている場合もよくあります。
class SomeSingleton {
private SomeSingleton _instance;
public SomeSingleton Instance {
get {
if(_instance == null) {
_instance =
FindObjectOfType<SomeSingleton>();
}
if(_instance == null) {
_instance = CreateSomeSingleton();
}
return _instance;
}
}
}
このパターンは基本的には問題ありませんが、シングルトンのオブジェクトが存在しない場合にアクセサが Scenesで確実に呼び出されるようにするため、入念にコードを調査する必要があります。欠落したシングルトンのインスタンスを getter が自動的に作成しない場合、往々にして、そのシングルトンを探すコードが FindObjectOfType
を繰り返し(1 フレームに複数回の場合も多い)呼び出す結果となり、パフォーマンスに余計な負荷が掛かります。
UnityEngine.Debug
ロギング API は非開発版ビルドから削除されたわけではなく、呼び出されるとログファイルに書き込みを行います。非開発版ビルドにデバッグ情報を意図的に書き込むことはほとんどないため、以下のように、開発版限定のロギングコールをカスタムメソッドでラップすることを推奨します。
public static class Logger {
[Conditional("ENABLE_LOGS")]
public static void Debug(string logMsg) {
UnityEngine.Debug.Log(logMsg);
}
}
これらのメソッドを [Conditional] 属性で decorate することで、 Conditional 属性の使用する define が、コンパイルされたソース内に decorate されたメソッドが含まれるかどうか決定します。
Conditional 属性に渡された define のどれも定義されていない場合は、 decorate されたメソッド に加えて、 その decorate されたメソッドへの全ての呼び出しがコンパイルされます。この効果は、メソッドとメソッドへの全ての呼び出しが #if … #endif
のプリプロセッサ ブロックでラップされた場合と全く同じになります。
Conditional
属性に関する詳細は、 MSDN ウェブサイト msdn.microsoft.com を参照してください。