アプリケーションのプロファイルをするときに、頻繁に遭遇する問題があります。このページでは、一般的なパフォーマンス問題の原因を調査する方法について概要を説明します。
起動時間のトレースを確認する場合、調査すべき重要なメソッドが 2 つあります。プロジェクトの設定、アセット、コードが起動時間へ与える影響に関しては、主にこの 2 つのメソッドが原因となります。
ノート: アプリケーションの起動時間は、プラットフォームによって異なります。ほとんどのプラットフォームでは、スプラッシュスクリーン が表示されている間に起動が行われます。
上の画像は、iOS デバイスで実行したサンプルプロジェクトの、インストルメント化されたトレースのスクリーンショットです。プラットフォーム別の startUnity
メソッド内の、 UnityInitApplicationGraphics
メソッドと UnityLoadApplication
メソッドに注目してください。
UnityInitApplicationGraphics
は多くの内部作業を行います。例えばグラフィックスデバイスの設定や、多くの Unity 内部システムの初期化などです。また、Resources システムに含まれる全てのファイルのインデックスをロードすることによって、Resources システム の初期化も行います。
Unity の Resource システム は、プロジェクトの Assets
フォルダーの Resources
フォルダー内のデータのすべての アセットファイル を含んでいます。これには、Resources
フォルダーの子フォルダーにあるすべてのファイルが含まれます。そのため、Resources システムの初期化に必要な時間は、アプリケーションプロジェクトの Resources
フォルダー内のファイル数に関連して増加します。
UnityLoadApplication
にはプロジェクトの最初のシーンをロードして初期化するメソッドが含まれます。これには,最初のシーンの表示に必要な全てのデータのデシリアライゼーションとインスタンス化 (例えば、シェーダーのコンパイル、テクスチャのアップロード、ゲームオブジェクトのインスタンス化) が含まれます。また、Unity は、最初のシーン内の全ての MonoBehaviour
の Awake
コールバックを実行します。
上記のようなプロセスになっているということは、つまり、プロジェクトの最初のシーン内の Awake
コールバック内に長時間掛かるコードがひとつでもある場合、そのコードが原因でプロジェクトの起動時間が長くなっている可能性があるこということです。これを解決するには、時間の掛かるコードを削除するか、そのコードをアプリケーションのライフサイクル内の他の場所で実行するようにする必要があります。
最初の起動時間の後にキャプチャされたプロファイリングトレースに関しては、主に注目すべきは PlayerLoop
メソッドです。これは Unity のメインループで、中にあるコードが 1 フレームに 1 回実行されます。
上のスクリーンショットは、PlayerLoop
内の最もパフォーマンスに影響を与えるメソッドのいくつかを示しています。ノート: PlayerLoop
内のメソッドの名前は、Unity のバージョンによって異なる場合があります。
PlayerRender
は、 Unity のレンダリングシステムを実行するメソッドです。これにはオブジェクトのカリング、動的バッチの計算、 GPU への描画指示のサブミットなどが含まれます。イメージエフェクトや、レンダリングベースのスクリプトコールバック (OnWillRenderObject など) は全てここで実行されます。基本的に、プロジェクトがインタラクティブな場合に最も CPU 時間を消費するのがこのメソッドです。
BaseBehaviourManager
はテンプレート化された 3 つのバージョンの CommonUpdate
を呼び出します。これらは、現在のシーンのアクティブなゲームオブジェクトに添付された MonoBehaviour
内の特定のコールバックを実行します。
CommonUpdate<UpdateManager>
は Update
コールバックを呼び出します。CommonUpdate<LateUpdateManager>
は LateUpdate
コールバックを呼び出します。CommonUpdate<FixedUpdateManager>
は、物理システムにチェックマークが入っている場合に FixedUpdate
を呼び出します。調査する上で基本的に最も役に立つメソッドファミリーは BaseBehaviourManager::CommonUpdate<UpdateManager>
です。 これが Unity プロジェクト内で実行される大部分のスクリプトコードのエントリーポイントだからです。
その他、検査に有効なメソッドがいくつかあります。
UI::CanvasManager
は、プロジェクトが UGUI system を使用している場合に、いくつかの異なるコールバックを実行します。これには Unity UI のバッチ計算やレイアウト更新が含まれます。プロファイラーに CanvasManager
が表示される場合、その原因として最もよく見られるのは、この 2 つの処理です。DelayedCallManager::Update
は コルーチン を実行します。PhysicsManager::FixedUpdate
は PhysX 物理システムを実行します。これには主に PhysX の内部コードの実行が含まれます。現在のシーン内の Rigidbody
や Collider
などの物理オブジェクトの数は、PhysX の内部コードに影響します。 物理ベースのコールバックもここに表示されます。具体的には、OnTriggerStay
と OnCollisionStay
です。プロジェクトが 2D 物理演算を使用している場合、それは、一式の類似した呼び出しとして Physics2DManager::FixedUpdate
の下に表示されます。
スクリプトが IL2CPP を用いてクロスコンパイルされてプラットフォーム上で実行されている場合、出力内で ScriptingInvocation
オブジェクトの含まれるラインを探してください。そこが、 Unity の内部ネイティブコードが、スクリプトコードを実行するためにスクリプトランタイムへ遷移している箇所です。ノート: 技術的には、Unity が IL2CPP を通じて C# コードを実行すると、そのコードもネイティブコードになります。 ただし、このクロスコンパイルされたコードは主に IL2CPP ランタイムフレームワーク経由でメソッドを実行しており、手書きの C++ には似ていません。
上のスクリーンショットでは、RuntimeInvoker_Void
行の下にネストされたメソッドは、Unity がフレームごとに 1 回実行するクロスコンパイルされた C# スクリプトの一部です。
これらのトレースラインの名前は、元のクラスの名前の後にアンダースコアと元のメソッドの名前が続きます。このサンプルのトレースでは、EventSystem.Update
、PlayerShooting.Update
、その他いくつかの Update
メソッドが確認できます。これらは、ほとんどの MonoBehaviours
に含まれる標準の Unity Update
コールバックです。
これらのメソッドを展開すると、その中のどのメソッドが CPU 時間を消費したかを確認できます。これには、プロジェクト内の他のスクリプトメソッド、Unity API、C# ライブラリ コードが含まれます。
上記のトレースは、StandaloneInputModule.Process
メソッドがフレームごとに 1 回、UI 全体を通じてレイキャストを行ったことを示しています。 このメソッドは、タッチイベントがマウスオーバーしたか、UI 要素をアクティブ化したかどうかを検出します。すべての UI 要素を反復処理し、マウスの位置が境界矩形内にあるかどうかをテストする方法は、リソースを大量に消費します。
CPU トレースでアセットのロードを特定することもできます。アセットのロードを示す主なメソッドは SerializedFile::ReadObject
です。 このメソッドは、バイナリデータストリームをファイルから Unity のシリアル化システムに接続します。このシステムは、Transfer
という名前のメソッドを介して動作します。Transfer
メソッドは、テクスチャ、MonoBehaviour、パーティクルシステムなどのすべてのアセットタイプにあります。
上のスクリーンショットでは、シーンを 1 つロード中です。シーンがロードされるとき、Unity は、そのシーン内の全てのアセットを読み込み、デシリアライズします。 SerializedFile::ReadObject
の下の各種 Transfer
メソッドがそれを示しています。
ランタイム中にパフォーマンスが低下し、パフォーマンストレースで SerializedFile::ReadObject
がかなりの時間を使用した場合は、アセットロードがフレームレートを低下させたことを意味します。ノート: SceneManager
、Resources
、または AssetBundle API が同期のアセットロードを要求すると、SerializedFile::ReadObject
が通常メインスレッドに表示されます。
このパフォーマンスの低下を解決するには、アセットのロードを非同期にするか (重い ReadObject
呼び出しをワーカースレッドに移す)、特定の重いアセットの事前にロードを行います。
Transfer
コールは、オブジェクトをクローンを作成する場合にも現れます (トレースでは CloneObject
メソッドがそれに当たります)。Transfer
への呼び出しが CloneObject
コールの下に表示される場合、Unity はアセットをストレージからロードしていません。その代わりに、古いオブジェクトのデータを新しいオブジェクトに移動します。 これを行うために、Unity は、古いオブジェクトをシリアライズし、その結果のデータを新しいオブジェクトとしてデシリアライズします。