Version: 2023.1
言語: 日本語
プロファイリングツール
ログファイル

プロファイラーのトレースを解析する

アプリケーションのプロファイルをするときに、頻繁に遭遇する問題があります。このページでは、一般的なパフォーマンス問題の原因を調査する方法について概要を説明します。

起動時の出力の分析

起動時間のトレースを確認する場合、調査すべき重要なメソッドが 2 つあります。プロジェクトの設定、アセット、コードが起動時間へ与える影響に関しては、主にこの 2 つのメソッドが原因となります。

ノート: アプリケーションの起動時間は、プラットフォームによって異なります。ほとんどのプラットフォームでは、スプラッシュスクリーン が表示されている間に起動が行われます。

iOS デバイス上で動作する Unity プロジェクトのサンプルのインストルメント化されたトレース
iOS デバイス上で動作する Unity プロジェクトのサンプルのインストルメント化されたトレース

上の画像は、iOS デバイスで実行したサンプルプロジェクトの、インストルメント化されたトレースのスクリーンショットです。プラットフォーム別の startUnity メソッド内の、 UnityInitApplicationGraphics メソッドと UnityLoadApplication メソッドに注目してください。

UnityInitApplicationGraphics は多くの内部作業を行います。例えばグラフィックスデバイスの設定や、多くの Unity 内部システムの初期化などです。また、Resources システムに含まれる全てのファイルのインデックスをロードすることによって、Resources システム の初期化も行います。

Unity の Resource システム は、プロジェクトの Assets フォルダーの Resources フォルダー内のデータのすべての アセットファイル を含んでいます。これには、Resources フォルダーの子フォルダーにあるすべてのファイルが含まれます。そのため、Resources システムの初期化に必要な時間は、アプリケーションプロジェクトの Resources フォルダー内のファイル数に関連して増加します。

UnityLoadApplication にはプロジェクトの最初のシーンをロードして初期化するメソッドが含まれます。これには,最初のシーンの表示に必要な全てのデータのデシリアライゼーションとインスタンス化 (例えば、シェーダーのコンパイル、テクスチャのアップロード、ゲームオブジェクトのインスタンス化) が含まれます。また、Unity は、最初のシーン内の全ての MonoBehaviourAwake コールバックを実行します。

上記のようなプロセスになっているということは、つまり、プロジェクトの最初のシーン内の Awake コールバック内に長時間掛かるコードがひとつでもある場合、そのコードが原因でプロジェクトの起動時間が長くなっている可能性があるこということです。これを解決するには、時間の掛かるコードを削除するか、そのコードをアプリケーションのライフサイクル内の他の場所で実行するようにする必要があります。

ランタイムトレースの分析

最初の起動時間の後にキャプチャされたプロファイリングトレースに関しては、主に注目すべきは PlayerLoop メソッドです。これは Unity のメインループで、中にあるコードが 1 フレームに 1 回実行されます。

Unity プロジェクトのサンプルのインストルメント化されたトレース
Unity プロジェクトのサンプルのインストルメント化されたトレース

上のスクリーンショットは、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 の内部コードの実行が含まれます。現在のシーン内の RigidbodyCollider などの物理オブジェクトの数は、PhysX の内部コードに影響します。 物理ベースのコールバックもここに表示されます。具体的には、OnTriggerStayOnCollisionStay です。

プロジェクトが 2D 物理演算を使用している場合、それは、一式の類似した呼び出しとして Physics2DManager::FixedUpdate の下に表示されます。

スクリプトメソッドの分析

スクリプトが IL2CPP を用いてクロスコンパイルされてプラットフォーム上で実行されている場合、出力内で ScriptingInvocation オブジェクトの含まれるラインを探してください。そこが、 Unity の内部ネイティブコードが、スクリプトコードを実行するためにスクリプトランタイムへ遷移している箇所です。ノート: 技術的には、Unity が IL2CPP を通じて C# コードを実行すると、そのコードもネイティブコードになります。 ただし、このクロスコンパイルされたコードは主に IL2CPP ランタイムフレームワーク経由でメソッドを実行しており、手書きの C++ には似ていません。

Unity プロジェクト例からのトレース
Unity プロジェクト例からのトレース

上のスクリーンショットでは、RuntimeInvoker_Void 行の下にネストされたメソッドは、Unity がフレームごとに 1 回実行するクロスコンパイルされた C# スクリプトの一部です。

これらのトレースラインの名前は、元のクラスの名前の後にアンダースコアと元のメソッドの名前が続きます。このサンプルのトレースでは、EventSystem.UpdatePlayerShooting.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 がかなりの時間を使用した場合は、アセットロードがフレームレートを低下させたことを意味します。ノート: SceneManagerResources、または AssetBundle API が同期のアセットロードを要求すると、SerializedFile::ReadObject が通常メインスレッドに表示されます。

このパフォーマンスの低下を解決するには、アセットのロードを非同期にするか (重い ReadObject 呼び出しをワーカースレッドに移す)、特定の重いアセットの事前にロードを行います。

Transfer コールは、オブジェクトをクローンを作成する場合にも現れます (トレースでは CloneObject メソッドがそれに当たります)。Transfer への呼び出しが CloneObject コールの下に表示される場合、Unity はアセットをストレージからロードしていません。その代わりに、古いオブジェクトのデータを新しいオブジェクトに移動します。 これを行うために、Unity は、古いオブジェクトをシリアライズし、その結果のデータを新しいオブジェクトとしてデシリアライズします。

プロファイリングツール
ログファイル