アプリケーションをプロファイルする時に、いくつかの一般的な問題が発生する可能性があります。このページでは、一般的なパフォーマンス問題の原因を調査する方法について説明します。
起動時間のプロファイリング出力を確認する場合、主に調査すべきメソッドは 2 つあります。UnityInitApplicationGraphics と UnityLoadApplication です。プロジェクトの設定、アセット、コードによる起動時間への影響に関しては、主にこの 2 つのメソッドが原因となります。
注意アプリケーションの起動時間はプラットフォームによって異なります。ほとんどのプラットフォームでは、起動は スプラッシュスクリーン の表示中に行われます。
上の iOS デバイスで実行したサンプル Unity プロジェクトの Instruments の出力のスクリーンショットで、プラットフォーム別の startUnity メソッド内の、UnityInitApplicationGraphics メソッドと UnityLoadApplication メソッドに注目してください。
UnityInitApplicationGraphics は多くの内部機能を実行します。例えばグラフィックスデバイスの設定や、多くの Unity 内部システムの初期化などです。また、リソースシステム に含まれる全てのファイルのインデックスをロードすることで、リソースシステムの初期化も行います。
Unity の リソースシステム には、プロジェクトの Assets フォルダー内の Resources フォルダーにあるデータ内の全ての アセットファイル が含まれます。これには、Resources フォルダーの子フォルダーにある全てのファイルが含まれます。したがって、リソースシステムの初期化に掛かる時間は、アプリケーションのプロジェクトの Resources フォルダー内のファイル数に応じて直線的に増加します。
UnityLoadApplication にはプロジェクトの最初のシーンをロードして初期化する (複数の) メソッドが含まれます。これには最初のシーンの表示に必要なデータのデシリアライゼーションとインスタンス化 (シェーダーのコンパイリング、テクスチャのアップロード、ゲームオブジェクトのインスタンス化など) が含まれます。また、Unity は最初のシーン内の全ての MonoBehaviour の Awake コールバックを実行します。
上記のようなプロセスになっているということは、つまり、プロジェクトの最初のシーン内の Awake コールバック内に長時間掛かるコードが 1 つでもある場合、そのコードが原因でプロジェクトの最初の起動時間が長くなっている可能性があるこということです。これを解決するには、時間の掛かるコードを削除するか、そのコードをアプリケーションのライフサイクル内の他の場所で実行するようにする必要があります。
最初の起動時間の後にキャプチャされたプロファイリング出力に関しては、主に注目すべきは 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 システム を使用している場合に、いくつかの異なるコールバックを実行します。これには 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 ラインの下にネストされたメソッドは、1 フレーム毎に 1 回実行されるクロスコンパイル済み C# スクリプトの一部です。
出力ラインの名前は、元のクラス名、下線、元のメソッド名の順で連なった形になっています。このサンプルの出力では、EventSystem.Update、PlayerShooting.Update、その他いくつかの Update メソッドが確認できます。これらは、ほとんどの MonoBehaviours 内に見られる、Unity の標準の Update コールバックです。
これらのメソッドを展開すると、その中のどのメソッドが CPU 時間を消費したかを確認できます。これは、プロジェクト内、Unity API 内、C# ライブラリコード内のその他のスクリプトメソッドに関しても同様に行えます。
上の出力からは、StandaloneInputModule.Process メソッドが 1 フレーム毎に 1 回、UI 全体にレイキャストを行っていたことが確認できます。このメソッドは、何らかのタッチイベントが UI 要素上をホバリングしたり要素をアクティベートしたりしていたかどうかを検知します。全ての UI 要素に対してメソッドを繰り返し、マウスの位置がその境界矩形内にあるかどうかをテストすると、高いリソース負荷が掛かります。
CPU の出力内でアセットのロードを特定することもできます。アセットのロードを示す主要なメソッドは SerializedFile::ReadObject です。このメソッドは特定のファイルからのバイナリデータストリームを、Transfer という名前のメソッドによって実行される Unity のシリアル化システムに接続します。Transfer メソッドは、テクスチャ、MonoBehaviour、パーティクルシステムを始めとする全てのアセットタイプに見られます。
上のスクリーンショットは、Unity がシーンをロードした出力です。シーンをロードすると、Unity はシーン内の全てのアセットを読み込み、デシリアライズします。これは、SerializedFile::ReadObject の下にあるさまざまな Transfer メソッドのコールによって示されます。
ランタイムにパフォーマンススタッターが発生し、パフォーマンス出力で SerializedFile::ReadObject がかなりの時間を費やしたことが示されている場合は、アセットのロードによってフレームレートが減少したことを意味します。ノート: SerializedFile::ReadObject は通常、SceneManager、Resources、または AssetBundle API がアセットの同期的ロードをリクエストすると、メインスレッドに表示されます。
このパフォーマンススタッターを解決するには、アセットのロードを非同期にする (重い ReadObject コールがワーカースレッドに移される) か、一部の重いアセットをプリロードしてください。
Transfer コールは、Unity がオブジェクトをクローン (複製) する場合にも現れます (出力内では CloneObject メソッドによって示されます)。Transfer への呼び出しが CloneObject コールの下に表示される場合、Unity は該当アセットをストレージからロードしていません。代わりに、Unity は古いオブジェクトのデータを新しいオブジェクトに送信します。Unity はこれを行うために、古いオブジェクトをシリアル化し、その結果のデータを新しいオブジェクトとしてデシリアライズします。