Resources フォルダー
特殊な最適化

最適化一般

パフォーマンスの問題に様々な原因があるように、コードの最適化方法も様々です。通常、 CPU の最適化を行うに当たっては、事前にアプリケーションの綿密なプロファイリングを行うことが強く推奨されます。ただし、どんな場合にも適用できる CPU 最適化手法もいくつかあります。

プロパティのアドレス指定を ID で行う

Unity は、アニメーターやマテリアルやシェーダーのプロパティのアドレスを内部的に指定する際、文字列名は使用しません。速度を高めるために、全てのプロパティ名はプロパティ ID 内にハッシュ化され、プロパティのアドレス指定にはこの ID が使用されます。

したがって、SetGet メソッドをアニメーターやマテリアルやシェーダーに使用している場合は、文字列型のメソッドではなく整数型のメソッドを使用してください。文字列型メソッドは単純に、文字列のハッシュ化を実行してから、ハッシュ化された ID をi整数型メソッドに転送します。

文字列のハッシュから作成されたプロパティ ID は、一回の実行中の間において決定的です。この使用方法として最も簡単なのは、静的な読み込み専用整数変数を各プロパティ名について宣言し、その整数変数を文字列の代わりに使用することです。これは、起動時に、更なる追加的な初期化コードを要することなく自動的に初期化されます。

アニメーターのプロパティ名に適した API は Animator.StringToHash、マテリアルとシェーダーのプロパティ名に適した API は Shader.PropertyToID です。

割り当てなしの物理演算 API を使用する

Unity 5.3 以降は、Physics クエリ API の割り当てなしのバージョンが提供されています。例えば RaycastAll コールは RaycastNonAlloc に置き換え、 SphereCastAll コールは SphereCastNonAlloc に置き換えることができます。 2D アプリケーションの場合は、全ての Physics2D クエリー API の割り当てなしのバージョンもあります。

Transform の操作

Transform の位置や角度が変更されると、その Transform と同じゲームオブジェクトおよび全ての子ゲームオブジェクトのコンポーネント全てに、 OnTransformChanged メッセージがディスパッチされます。このため、 1 フレーム内における Transform の位置と角度の変更回数はなるべく少なくするのが賢明です。特定の Transform への適用が必要になる(可能性のある)変更全てをバッチ化して一度に適用することをお勧めします。これにより、その Transform ヒエラルキー全体に伝播されなければならない OnTransformChanged メッセージの数を最小限に抑えられます。これは、アニメーションキャラクターなど、深い(大きな) Transform ヒエラルキーを動かす場合には特に重要です。

ベクター、四元数計算および処理順序

タイトなループ内にあるベクターや四元数計算に関しては、「整数演算のほうが浮動小数点演算よりも速く、浮動小数点演算のほうがベクターやマトリックス演算や四元数計算よりも速い」ということを覚えておきましょう。

このため、可換演算あるいは結合演算の可能な範囲では常に、 個々の計算処理のコストを最小限に抑えるようにしてください。


Vector3 x;

int a, b;

// 非効率的(ベクターの乗算が 2 つ発生する)

Vector3 slow = a * x * b;

// 効率的(整数の乗算が 1 つ、 ベクターの乗算が 1 つ)

Vector3 fast = a * b * x;

ビルトインの ColorUtility

HTML の文字列方式 (#RRGGBBAA) と Unity ネイティブの ColorColor32 方式との間で色指定の方式の変換を行わなければならないアプリケーションでは、 Unity Community のスクリプトが一般的に使用されています。このスクリプトは速度が遅く、また文字列操作のために相当な量のメモリ割り当てを発生させるものでした。

Unity 5 以降は、この変換を効率的に実行できる ColorUtility API がビルトインで提供されています。可能な限り、こちらの API をご使用ください。

Find と FindObjectOfType

基本的には、完成版のコードでは Object.FindObject.FindObjectOfType は一切使用しないことが推奨されます。これらの API を使用すると、 Unity はメモリ内の全てのゲームオブジェクトとコンポーネントの反復処理を行わなければならず、プロジェクトの規模が少し拡大すると効率性が大幅に低下します。

シングルトン オブジェクトのアクセサの場合はこの限りではありません。グローバル マネージャー オブジェクトは “instance” プロパティをアクセス可能にすることがよくあり、シングルトンの既存のインスタンスを検知する getter 内に FindObjectOfType コールを持っている場合もよくあります。


class SomeSingleton {

    private SomeSingleton _instance;

    public SomeSingleton Instance {

        get {

            if(_instance == null) { 

                _instance =

                    FindObjectOfType<SomeSingleton>(); 

            }

            if(_instnace == null) { 

                _instance = CreateSomeSingleton();

            }

            return _instance;

        }

    }

}

このパターンは基本的には問題ありませんが、シングルトンのオブジェクトが存在しない場合にアクセサが Scenesで確実に呼び出されるようにするため、入念にコードを調査する必要があります。欠落したシングルトンのインスタンスを getter が自動的に作成しない場合、往々にして、そのシングルトンを探すコードが FindObjectOfType を繰り返し(1 フレームに複数回の場合も多い)呼び出す結果となり、パフォーマンスに余計な負荷が掛かります。

カメラの位置

内部的には、 Unity の Camera.main プロパティは、 Object.FindObject の特殊なバリアントである Object.FindObjectWithTag を呼び出します。このプロパティへのアクセスは Object.FindObjectOfType への呼び出しよりも効率的という訳ではありません。どうしてもメインカメラがコードでアドレス指定されなければならない場合は、次の二つのうちのどちらかを行うことが強く推奨されます。

  • Start または OnEnable コールバック内で Camera.main にアクセスし、結果の参照をキャッシュする。

  • アクティブなカメラを提供または投入できる Camera Manager クラスを作成する。

デバッグコードと [conditional] 属性

UnityEngine.Debug ロギング API は非開発版ビルドが基になったものではなく、呼び出されるとログファイルに書き込みを行います。非開発版ビルドにデバッグ情報を意図的に書き込むことはほとんどありませんので、以下のように、開発版限定のロギングコールをカスタムメソッドでラップすることをお勧めします。


    public static class Logger {

            [Conditional("ENABLE_LOGS")]

            public void Debug(string logMsg) {

                Debug.Log(logMsg);

            }

        }

これらのメソッドを [Conditional] 属性で decorate することで、 Conditional 属性の使用する define が、コンパイルされたソース内に decorate されたメソッドが含まれるかどうか決定します。

Conditional 属性に渡された define のどれも定義されていない場合は、 decorate されたメソッド に加えて、 その decorate されたメソッドへの全ての呼び出しがコンパイルされます。この効果は、メソッドとメソッドへの全ての呼び出しが #if … #endif のプリプロセッサ ブロックでラップされた場合と全く同じになります。

Conditional 属性に関する詳細は、 MSDN ウェブサイト msdn.microsoft.com でご確認ください。

Resources フォルダー
特殊な最適化