Hero background image

ゲーム開発者のためのパフォーマンスプロファイリングのヒント

このウェブページは、お客様の便宜のために機械翻訳されたものです。翻訳されたコンテンツの正確性や信頼性は保証いたしかねます。翻訳されたコンテンツの正確性について疑問をお持ちの場合は、ウェブページの公式な英語版をご覧ください。

スムーズなパフォーマンスは、プレイヤーに没入感のあるゲーム体験を提供するために不可欠です。ゲームを最適化するためには、一貫したエンドツーエンドのプロファイリングワークフローが「必須」であり、効率的なゲーム開発のために、シンプルな三段階の手順から始まります:

  • 大きな変更を加える前にプロファイルを取る:ベースラインを確立する。
  • 開発中のプロファイリング:変更がパフォーマンスや予算を壊さないように追跡し、確認する。
  • 後でプロファイルを取る:変更が望ましい効果を持っていたことを証明する。

このページでは、ゲーム開発者のための一般的なプロファイリングワークフローを概説しています。これは、Unityゲームのプロファイリングに関する究極のガイドの電子書籍から抜粋したもので、無料でダウンロード可能です(ガイドのUnity 6バージョンは近日中に利用可能になります)。この電子書籍は、ゲーム開発、プロファイリング、最適化における外部および内部のUnity専門家によって作成されました。

この記事では、プロファイリングで設定するのに役立つ目標、CPUバウンドやGPUバウンドなどの一般的なパフォーマンスボトルネック、これらの状況をより詳細に特定し調査する方法について学ぶことができます。

フレーム予算を設定する

ゲーマーはしばしばフレームレート、つまり1 秒あたりのフレーム数(fps)を使用してパフォーマンスを測定しますが、開発者としては一般的にフレーム時間(ミリ秒)を使用することが推奨されます。次の簡略化されたシナリオを考えてみてください:

ランタイム中に、あなたのゲームは0.75秒で59フレームをレンダリングします。しかし、次のフレームはレンダリングに0.25秒かかります。平均的に提供されるフレームレートは60 fpsで良さそうに聞こえますが、実際には最後のフレームがレンダリングに四分の一秒かかるため、プレイヤーはスタッター効果に気付くでしょう。

これが、フレームごとの特定の時間バジェットを目指すことが重要な理由の一つです。これにより、ゲームのプロファイリングと最適化に向けて取り組むための確固たる目標が提供され、最終的にはプレイヤーにとってよりスムーズで一貫した体験を生み出します。

各フレームには、ターゲット fps に基づいた時間バジェットがあります。30 fps をターゲットにしたアプリケーションは、常にフレームごとに33.33 ms未満(1000 ms / 30 fps)である必要があります。同様に、60 fps のターゲットではフレームごとに16.66 ms(1000 ms / 60 fps)が残ります。

UI メニューの表示やシーンの読み込みなど、非インタラクティブなシーケンス中はこのバジェットを超えることができますが、ゲームプレイ中は超えてはいけません。ターゲットフレームバジェットを超える単一のフレームでも、ヒッチを引き起こします。

注:VR ゲームで一貫して高いフレームレートを維持することは、プレイヤーに吐き気や不快感を引き起こさないために不可欠であり、プラットフォームホルダーからの認証を得るためにしばしば必要です。

1 秒あたりのフレーム数:誤解を招く指標

ゲーマーがパフォーマンスを測定する一般的な方法は、フレームレート、または1 秒あたりのフレーム数です。しかし、代わりにフレーム時間(ミリ秒)を使用することが推奨されます。なぜそうなのかを理解するために、上記の fps とフレーム時間のグラフを見てください。

これらの数字を考慮してください:

1000 ms/秒 / 900 fps = 1.111 ms/フレーム

1000 ms/秒 / 450 fps = 2.222 ms/フレーム

1000 ms/秒 / 60 fps = 16.666 ms/フレーム

1000 ms/秒 / 56.25 fps = 17.777 ms/フレーム

アプリケーションが900 fpsで動作している場合、これは1フレームあたり1.111ミリ秒のフレーム時間に相当します。450 fpsでは、これは1フレームあたり2.222ミリ秒です。これは、フレームレートが半分に見えるにもかかわらず、1フレームあたりわずか1.111ミリ秒の違いを表しています。

60 fpsと56.25 fpsの違いを見ると、それぞれ16.666ミリ秒/フレームと17.777ミリ秒/フレームに相当します。これも1フレームあたり1.111ミリ秒の追加を表していますが、ここではフレームレートの低下は割合的にあまり劇的に感じません。

これが、開発者がfpsではなく平均フレーム時間を使用してゲームの速度をベンチマークする理由です。

ターゲットフレームレートを下回らない限り、fpsを心配する必要はありません。ゲームがどれだけ速く動いているかを測定するためにフレーム時間に焦点を当て、その後フレーム予算内に留まるようにしてください。

詳細については、元の記事「ロバート・ダンロップのfps対フレーム時間」をお読みください。

FPS フレームレートのグラフ
fps対フレーム時間

モバイルの課題

熱制御は、モバイルデバイス向けのアプリケーションを開発する際に最適化する最も重要な分野の1つです。CPUまたはGPUが非効率的なコードのためにフルスロットルで長時間動作すると、これらのチップは熱くなります。過熱とチップへの潜在的な損傷を避けるために、オペレーティングシステムはデバイスのクロックスピードを下げて冷却を許可し、フレームのスタッタリングと悪いユーザー体験を引き起こします。このパフォーマンスの低下は、サーマルスロットリングとして知られています。

高いフレームレートと増加したコード実行(またはDRAMアクセス操作)は、バッテリーの消耗と熱生成の増加を引き起こします。悪いパフォーマンスは、低価格のモバイルデバイスの全セグメントでゲームをプレイできなくする可能性があり、市場機会を逃すことにつながります。

熱の問題に取り組む際は、システム全体の予算として作業するための予算を考慮してください。

サーマルスロットリングとバッテリー消耗に対抗するために、早期にプロファイリングを行い、ゲームを最初から最適化します。熱とバッテリー消耗の問題に対抗するために、ターゲットプラットフォームのハードウェアに合わせてプロジェクト設定を調整してください。

モバイルのフレーム予算を調整する

デバイスの熱問題に対抗するための一般的なヒントは、約35%のフレームアイドル時間を残すことです。これにより、モバイルチップが冷却され、過剰なバッテリー消耗を防ぐのに役立ちます。1フレームあたり33.33 ms(30 fpsの場合)のターゲットフレーム時間を使用すると、モバイルデバイスのフレーム予算は約1フレームあたり22 msになります。

計算は次のようになります:(1000 ms / 30) * 0.65 = 21.66 ms

同じ計算を使用してモバイルで60 fpsを達成するには、ターゲットフレーム時間が(1000 ms / 60) * 0.65 = 10.83 msが必要です。これは多くのモバイルデバイスで達成するのが難しく、30 fpsをターゲットにするよりもバッテリーを2倍早く消耗します。これらの理由から、多くのモバイルゲームは60 fpsではなく30 fpsをターゲットにしています。この設定を制御するにはApplication.targetFrameRateを使用し、フレーム時間に関する詳細はプロファイリングの電子書籍の「フレーム予算を設定する」セクションを参照してください。

モバイルチップの周波数スケーリングは、プロファイリング時にフレームアイドル時間の予算配分を特定するのを難しくすることがあります。あなたの改善と最適化は純粋にプラスの効果を持つ可能性がありますが、モバイルデバイスは周波数を下げているかもしれず、その結果、冷却効果が得られます。最適化の前後でモバイルチップの周波数、アイドル時間、スケーリングを監視するために、FTracePerfettoなどのカスタムツールを使用してください。

ターゲットfps(例えば30 fpsの場合は33.33 ms)の総フレーム時間予算内に留まり、デバイスがこのフレームレートを維持するために少なく働いているか、温度が低いことを記録している限り、正しい方向に進んでいます。

モバイルデバイスのフレーム予算に余裕を持たせるもう一つの理由は、実際の温度変動を考慮するためです。暑い日には、モバイルデバイスが加熱し、熱を放散するのが難しくなり、サーマルスロットリングやゲームパフォーマンスの低下につながる可能性があります。フレーム予算の一部を確保して、このシナリオを回避するのに役立ててください。

FTrace や Perfetto などのツールを使用して CPU の周波数とアイドル状態を監視することは、フレーム予算割り当ての最適化の結果を確認するのに役立ちます。
FTrace や Perfetto などのツールを使用して CPU の周波数とアイドル状態を監視することは、フレーム予算割り当ての最適化の結果を確認するのに役立ちます。

メモリアクセス操作を減らす

DRAMアクセスは、モバイルデバイスでは通常、電力を多く消費する操作です。Armのモバイルデバイス上のグラフィックスコンテンツの最適化アドバイスによれば、LPDDR4メモリアクセスはバイトあたり約100ピコジュールのコストがかかります。

フレームごとのメモリアクセス操作の数を減らすには:

  • フレームレートを下げる
  • 可能な場合はディスプレイ解像度を下げる
  • 頂点数と属性精度を減らしたシンプルなメッシュを使用する
  • テクスチャ圧縮とミップマッピングを使用する

Arm CPUまたはGPUハードウェアを活用するデバイスに焦点を当てる必要がある場合、Arm Performance Studioツール(具体的には、Streamline Performance Analyzer)には、メモリ帯域幅の問題を特定するための優れたパフォーマンスカウンターが含まれています。利用可能なカウンターは、対応するユーザーガイドにおいて、各Arm GPU世代ごとにリストされ、説明されています。例えば、Mali-G710パフォーマンスカウンターリファレンスガイド。Arm Performance Studio GPUプロファイリングには、Arm ImmortalisまたはMali GPUが必要です。

ベンチマーク用のハードウェアティアを確立する

プラットフォーム固有のプロファイリングツールを使用することに加えて、サポートしたい各プラットフォームと品質のティアまたは最低スペックデバイスを確立し、これらの仕様ごとにパフォーマンスをプロファイリングおよび最適化します。

例えば、モバイルプラットフォームをターゲットにしている場合、ターゲットハードウェアに基づいて機能をオンまたはオフに切り替える品質管理を持つ3つのティアをサポートすることを決定するかもしれません。次に、各ティアの最低デバイス仕様に最適化します。別の例として、コンソール向けのゲームを開発している場合は、古いバージョンと新しいバージョンの両方でプロファイリングを行うことを確認してください。

私たちの最新のモバイル最適化ガイドには、モバイルデバイスでゲームを実行する際にサーマルスロットリングを減らし、バッテリー寿命を延ばすのに役立つ多くのヒントとトリックがあります。

メモリアクセス操作を減らす
Arm の Streamline Performance Analyzer には、ターゲットである Arm ハードウェアで行うプロファイリングのライブセッション中に収集できる、豊富なパフォーマンスカウンター情報が用意されています。これは、オーバードローから生じるメモリ帯域幅の飽和などのパフォーマンス問題を特定するのに役立ちます。

高レベルから低レベルのプロファイリングへ

プロファイリングを行う際は、最大の影響を与えることができる領域に時間と労力を集中させることを確認したいです。したがって、プロファイリングを行う際は、レンダリング、スクリプト、物理学、ガベージコレクション(GC)割り当てなどのカテゴリの高レベルの概要から始めるトップダウンアプローチを推奨します。懸念のある領域を特定したら、より深い詳細に掘り下げることができます。この高レベルのパスを使用して、データを収集し、不要なマネージ割り当てやコアゲームループでの過剰なCPU使用を引き起こすシナリオを含む、最も重要なパフォーマンス問題についてメモを取ります。

まず、GC.Allocマーカーのコールスタックを収集する必要があります。このプロセスに不慣れな場合は、電子書籍の「アプリケーションの生存期間にわたる繰り返しメモリ割り当ての特定」というセクションでいくつかのヒントやコツを見つけてください。

報告されたコールスタックが割り当てのソースや他の遅延を追跡するのに十分に詳細でない場合は、割り当てのソースを見つけるために、Deep Profilingを有効にして2回目のプロファイリングセッションを実行できます。深いプロファイリングについては電子書籍で詳しく説明していますが、要約すると、これはプロファイラーのモードで、すべての関数呼び出しの詳細なパフォーマンスデータをキャプチャし、実行時間や動作に関する詳細な洞察を提供しますが、標準的なプロファイリングに比べてかなり高いオーバーヘッドがあります。

フレーム時間の「犯人」についてメモを収集する際は、他のフレームと比較してどのように比較されるかを必ず記録してください。深いプロファイリングが有効になっていると、この相対的な影響が歪む可能性があります。なぜなら、深いプロファイリングはすべてのメソッド呼び出しを計測することによってかなりのオーバーヘッドを追加するからです。

早期にプロファイルする

プロジェクトの開発サイクル全体を通じて常にプロファイリングを行うべきですが、プロファイリングから得られる最も重要な利益は、初期段階で始めるときに得られます。

早期に頻繁にプロファイリングを行い、あなたとあなたのチームがプロジェクトの「パフォーマンスシグネチャ」を理解し、記憶できるようにします。パフォーマンスが急落した場合、問題が発生したときに簡単に特定し、修正することができます。

エディターでのプロファイリングは主要な問題を特定する簡単な方法を提供しますが、最も正確なプロファイリング結果は、ターゲットデバイスでビルドを実行しプロファイリングすることから得られ、各プラットフォームのハードウェア特性を掘り下げるためにプラットフォーム固有のツールを活用することが必要です。この組み合わせにより、すべてのターゲットデバイスにわたるアプリケーションパフォーマンスの全体的なビューが提供されます。例えば、あるモバイルデバイスではGPUに制約があるかもしれませんが、他のデバイスではCPUに制約があるかもしれません。これを測定することでのみ学ぶことができます。

パフォーマンスの問題を特定する

このチャートの印刷可能なPDFバージョンをこちらからダウンロードしてください。

プロファイリングの目的は、最適化のターゲットとしてボトルネックを特定することです。推測に頼ると、ボトルネックでないゲームの部分を最適化してしまい、全体的なパフォーマンスにほとんど改善が見られない結果になることがあります。一部の「最適化」は、ゲームの全体的なパフォーマンスを悪化させる可能性があり、他のものは手間がかかるが重要な結果をもたらさないことがあります。重要なのは、集中した時間投資の影響を最適化することです。

上のフローチャートは、初期のプロファイリングプロセスを示しており、その後のセクションでは各ステップに関する詳細情報を提供しています。また、実際のUnityプロジェクトからのプロファイラーキャプチャを提示し、注目すべき事柄を示しています。

CPUがGPUを待っているときも含め、すべてのCPUアクティビティの全体像を把握するには、プロファイラーのタイムラインビューを使用してください。キャプチャを正しく解釈するために、一般的なプロファイラーマーカーに慣れておきましょう。プロファイラーマーカーの一部は、ターゲットプラットフォームによって異なる場合があるため、各ターゲットプラットフォームでゲームのキャプチャを探る時間を費やし、プロジェクトにとって「正常な」キャプチャがどのようなものかを把握してください。

プロジェクトのパフォーマンスは、最も時間がかかるチップおよび/またはスレッドによって制約されます。最適化の努力はその領域に集中すべきです。例えば、ターゲットフレーム時間のバジェットが33.33msでVSyncが有効なゲームの次のシナリオを想像してください:

  • CPUフレーム時間(VSyncを除く)が25msでGPU時間が20msの場合、問題ありません!CPUに制約されていますが、すべてがバジェット内であり、最適化してもフレームレートは改善されません(CPUとGPUの両方を16.66ms未満にして60fpsにジャンプしない限り)。
  • CPUフレーム時間が40msでGPUが20msの場合、CPUに制約されており、CPUパフォーマンスを最適化する必要があります。GPUパフォーマンスを最適化しても役に立ちません。実際、適用可能な場合はC#コードの代わりにコンピュートシェーダーを使用して、CPUの作業の一部をGPUに移動させることを検討するかもしれません。
  • CPUフレーム時間が20msでGPUが40msの場合、GPUに制約されており、GPUの作業を最適化する必要があります。
  • CPUとGPUの両方が40msの場合、両方に制約されており、30fpsに到達するためには両方を33.33ms未満に最適化する必要があります。

CPUまたはGPUに制約されていることをさらに探るこれらのリソースを参照してください:

このフローチャートに従い、プロファイラーを使用して最適化の努力を集中させる場所を特定してください。
このフローチャートに従い、プロファイラーを使用して最適化の努力を集中させる場所を特定してください。

フレームバジェットに収まっているか?

プロジェクトを早期に、かつ開発の過程で頻繁にプロファイリングおよび最適化することで、アプリケーションのすべてのCPUスレッドと全体のGPUフレーム時間がフレームバジェット内に収まることを確保できます。このプロセスを導く質問は、フレームバジェット内にいるのかどうかです。

上記は、継続的なプロファイリングと最適化を行ったチームが開発したUnityモバイルゲームのプロファイルキャプチャの画像です。このゲームは、高スペックのモバイルフォンで60 fps、キャプチャにあるような中/低スペックのフォンで30 fpsを目指しています。

選択されたフレームの時間のほぼ半分が、黄色のWaitForTargetFPSプロファイラーマーカーによって占められていることに注意してください。アプリケーションはApplication.targetFrameRateを30 fpsに設定しており、VSyncが有効になっています。メインスレッドでの実際の処理作業は約19 msで終了し、次のフレームを開始する前に33.33 msの残りの時間が経過するのを待つために残りの時間が費やされます。この時間はプロファイラーマーカーで表されていますが、メインCPUスレッドはこの時間の間、実質的にアイドル状態であり、CPUが冷却され、最小限のバッテリー電力を使用できるようにしています。

他のプラットフォームやVSyncが無効になっている場合、注目すべきマーカーは異なるかもしれません。重要なのは、メインスレッドがフレームバジェット内で実行されているか、アプリケーションがVSyncを待っていることを示す何らかのマーカーでフレームバジェットに正確にあるか、他のスレッドにアイドル時間があるかを確認することです。

アイドル時間は、灰色または黄色のプロファイラーマーカーで表されます。上記のスクリーンショットは、レンダースレッドがGfx.WaitForGfxCommandsFromMainThreadでアイドル状態であることを示しており、これは1フレームでGPUにドローコールを送信し終え、次のフレームでCPUからのさらなるドローコールリクエストを待っている時間を示します。同様に、Job Worker 0スレッドがCanvas.GeometryJobでいくつかの時間を費やしているものの、ほとんどの時間はアイドル状態です。これらはすべて、フレームバジェット内で快適に動作しているアプリケーションの兆候です。

ゲームがフレームバジェット内にある場合

フレームバジェット内にいる場合、バッテリー使用量やサーマルスロットリングを考慮してバジェットに加えた調整を含めて、主要なプロファイリングタスクは完了です。アプリケーションがメモリバジェット内にもあることを確認するために、Memory Profilerを実行して結論を出すことができます。

上記の画像は、30 fpsに必要な約22 msのフレームバジェット内で快適に動作しているゲームを示しています。メインスレッドの時間をVSyncまでパディングしているWaitForTargetfpsと、レンダースレッドおよびワーカースレッドの灰色のアイドル時間に注意してください。また、Gfx.Presentのフレームの終了時間を見てVBlankインターバルを観察でき、これらの間の時間を測定するためにTimelineエリアや上部の時間ルーラーに時間スケールを描くことができます。

フレームバジェットに収まっているか?
これは、オーバーヒートを起こさずに30 fpsに必要な約22 msのフレームバジェット内で快適に動作しているゲームのプロファイルです。メインスレッドの時間をVSyncまでパディングしているWaitForTargetfpsと、レンダースレッドおよびワーカースレッドの灰色のアイドル時間に注意してください。また、Gfx.Presentのフレームの終了時間を見てVBlankインターバルを観察でき、これらの間の時間を測定するためにTimelineビューや上部の時間ルーラーに時間スケールを描くことができます。

CPUバウンド

ゲームがCPUフレームバジェット内にない場合、次のステップはCPUのどの部分がボトルネックになっているかを調査することです。言い換えれば、どのスレッドが最も忙しいかです。

全体のCPU負荷がボトルネックになることは稀です。現代のCPUは、独立して同時に作業を行うことができる複数のコアを持っています。異なるスレッドは各CPUコアで実行できます。完全なUnityアプリケーションは、さまざまな目的のためにさまざまなスレッドを使用しますが、パフォーマンスの問題を見つけるために最も一般的なものは次のとおりです:

  • メインスレッド:ここが、ゲームロジック/スクリプトの大部分がデフォルトで作業を行う場所です。物理、アニメーション、UI、レンダリングの初期段階など、ほとんどのUnityシステムはここで実行されます。
  • レンダースレッド:これは、レンダリング指示をGPUに送信する前に発生しなければならない準備作業(例:シーン内のどのオブジェクトがカメラに見えるか、視錐台の外にあるために除外/非表示にされているオブジェクト、他の基準によって隠されているオブジェクト)を処理します。
  • レンダリングプロセス中、メインスレッドはシーンを調べ、カメラカリング、深度ソート、ドローコールバッチ処理を行い、レンダリングするもののリストを生成します。このリストはレンダースレッドに渡され、Unityの内部プラットフォーム非依存の表現から、特定のプラットフォームでGPUに指示を出すために必要な特定のグラフィックスAPI呼び出しに変換されます。
  • ジョブワーカースレッド:開発者は、メインスレッドの負荷を軽減するために、ワーカースレッドで実行される特定の種類の作業をスケジュールするためにジョブシステムを利用できます。Unityのシステムや機能のいくつかも、物理、アニメーション、レンダリングなどのジョブシステムを利用しています。

メインスレッド最適化の実世界の例

以下の画像は、メインスレッドに制約のあるプロジェクトがどのように見えるかを示しています。このプロジェクトはMeta Quest 2で実行されており、通常は13.88 ms(72 fps)または8.33 ms(120 fps)のフレームバジェットをターゲットにしています。高いフレームレートはVRデバイスでの動きの病気を避けるために重要です。しかし、このゲームが30 fpsをターゲットにしていたとしても、このプロジェクトが問題に直面していることは明らかです。

レンダースレッドとワーカースレッドは、フレームバジェット内の例と似ているように見えますが、メインスレッドはフレーム全体にわたって明らかに作業で忙しいです。フレームの最後にプロファイラーのオーバーヘッドの少量を考慮しても、メインスレッドは45ミリ秒以上忙しく、つまりこのプロジェクトは22 fps未満のフレームレートを達成しています。メインスレッドがVSyncを待っていることを示すマーカーはなく、フレーム全体で忙しいです。

次の調査段階は、フレームの中で最も時間がかかる部分を特定し、なぜそうなっているのかを理解することです。このフレームでは、PostLateUpdate.FinishFrameRenderingが16.23ミリ秒かかり、フレームの予算全体を超えています。詳細に調査すると、Inl_RenderCameraStackというマーカーが5つ存在し、5つのカメラがアクティブでシーンをレンダリングしていることを示しています。Unityのすべてのカメラがカリング、ソート、バッチ処理を含む全レンダーパイプラインを呼び出すため、このプロジェクトの最優先タスクはアクティブなカメラの数を減らすことで、理想的には1つだけにすることです。

BehaviourUpdateは、すべてのMonoBehaviour.Update()メソッドの実行を含むプロファイラーマーカーで、このフレームでは7.27ミリ秒かかります。

タイムラインビューでは、マゼンタ色のセクションがスクリプトがマネージヒープメモリを割り当てているポイントを示します。Hierarchyビューに切り替え、検索バーにGC.Allocと入力してフィルタリングすると、このメモリの割り当てに約0.33ミリ秒かかることが示されます。しかし、それはメモリ割り当てがCPUパフォーマンスに与える影響の不正確な測定です。

GC.Allocマーカーは、典型的なプロファイラサンプルのように開始点と終了点を記録することで時間を計測されません。オーバーヘッドを最小限に抑えるために、Unityは割り当てのタイムスタンプと割り当てられたサイズのみを記録します。

プロファイラーは、GC.Allocマーカーに小さな人工的なサンプル継続時間を割り当てて、プロファイラービューで可視化されるようにしています。実際の割り当てには、特に新しいメモリ範囲をシステムから要求する必要がある場合、より長くかかることがあります。影響をより明確に見るために、割り当てを行うコードの周りにプロファイラーマーカーを配置してください。深いプロファイリングでは、タイムラインビューのマゼンタ色のGC.Allocサンプルの間の隙間が、どれくらいの時間がかかったかの指標を提供します。

さらに、新しいメモリを割り当てることは、測定が難しく、直接的に帰属させることができないパフォーマンスに悪影響を及ぼす可能性があります。

  • システムから新しいメモリを要求すると、モバイルデバイスの電力予算に影響を与え、システムがCPUまたはGPUの速度を遅くする可能性があります。
  • 新しいメモリは、CPUのL1キャッシュにロードされる必要があり、既存のキャッシュラインを押し出すことになります。
  • インクリメンタルまたは同期的なガベージコレクションは、マネージメモリ内の既存の空きスペースが最終的に超過されると、直接または遅延でトリガーされる可能性があります。

フレームの開始時に、4つのPhysics.FixedUpdateのインスタンスが合計で4.57ミリ秒になります。その後、LateBehaviourUpdate(MonoBehaviour.LateUpdate()への呼び出し)が4ミリ秒かかり、Animatorsが約1ミリ秒を占めます。このプロジェクトが望ましいフレームバジェットとレートに達するためには、すべてのメインスレッドの問題を調査して適切な最適化を見つける必要があります。

メインスレッドボトルネックの一般的な落とし穴

最も時間がかかるものを最適化することで、最大のパフォーマンス向上が得られます。以下の領域は、メインスレッドにバウンドしているプロジェクトで最適化を探すのにしばしば有益な場所です:

  • 物理計算
  • MonoBehaviourスクリプトの更新
  • ガーベジ割り当ておよび/またはコレクション
  • メインスレッドでのカメラカリングとレンダリング
  • 非効率的なドローコールバッチ処理
  • UIの更新、レイアウト、および再構築
  • アニメーション

最も一般的な落とし穴を最適化するための実行可能なヒントの長いリストを提供する最適化ガイドをお読みください:

調査したい問題に応じて、他のツールも役立つ場合があります:

  • 長時間かかるがその理由が正確にわからないMonoBehaviourスクリプトには、コードにプロファイラーマーカーを追加するか、ディーププロファイリングを試して完全なコールスタックを確認してください。
  • マネージメモリを割り当てるスクリプトの場合、割り当てがどこから来ているのか正確に見るために割り当てコールスタックを有効にしてください。または、ディーププロファイリングを有効にするか、プロジェクトオーディターを使用して、メモリでフィルタリングされたコードの問題を表示し、マネージ割り当てを引き起こすすべてのコード行を特定できます。
  • フレームデバッガーを使用して、悪いドローコールバッチ処理の原因を調査します。

ゲームの最適化に関する包括的なヒントについては、これらのUnity専門ガイドを無料でダウンロードしてください:

CPU バウンド
メインスレッドにバウンドしているプロジェクトからのキャプチャ

CPUバウンド:レンダースレッド

ここに、レンダースレッドに束縛された実際のプロジェクトがあります。これは、等角視点を持ち、ターゲットフレームバジェットが33.33msのコンソールゲームです。

プロファイラーキャプチャは、現在のフレームのレンダリングを開始する前に、メインスレッドがレンダースレッドを待機していることを示しています。これは、Gfx.WaitForPresentOnGfxThread マーカーによって示されています。レンダースレッドは、前のフレームからのドローコールコマンドをまだ送信しており、メインスレッドからの新しいドローコールを受け入れる準備ができていません。また、Camera.Renderで時間を費やしています。

現在のフレームに関連するマーカーと他のフレームからのマーカーの違いを見分けることができます。後者は暗く表示されます。メインスレッドが続行できるようになり、レンダースレッドが処理するためのドローコールを発行し始めると、レンダースレッドは現在のフレームを処理するのに100ms以上かかり、次のフレーム中にボトルネックを作成します。

さらなる調査により、このゲームは9つの異なるカメラと置き換えシェーダーによって引き起こされる多くの追加パスを含む複雑なレンダリングセットアップを持っていることが示されました。ゲームは、フォワードレンダリングパスを使用して130以上のポイントライトをレンダリングしており、各ライトに対して複数の追加の透明ドローコールを追加する可能性があります。合計で、これらの問題が組み合わさって、フレームごとに3000以上のドローコールを生成しました。

レンダースレッドボトルネックの一般的な落とし穴

以下は、レンダースレッドに束縛されたプロジェクトで調査すべき一般的な原因です:

  • ドローコールバッチ処理が不十分:これは、OpenGLやDirectX 11などの古いグラフィックスAPIに特に当てはまります。
  • カメラが多すぎる:分割画面のマルチプレイヤーゲームを作成していない限り、アクティブなカメラは1つだけであるべきです。
  • カリングが不十分:これにより、描画されるものが多すぎます。カメラのフラスタムの寸法とカリングレイヤーマスクを調査してください。

レンダリングプロファイラモジュールは、毎フレームのドローコールバッチとSetPassコールの数の概要を示します。レンダースレッドがGPUに発行しているドローコールバッチを調査するための最良のツールは、フレームデバッガーです。

特定されたボトルネックを解決するためのツール

この電子書籍の焦点はパフォーマンスの問題を特定することですが、以前に強調した2つの補完的なパフォーマンス最適化ガイドは、ターゲットプラットフォームがPCまたはコンソールモバイルかに応じてボトルネックを解決するための提案を提供します。レンダースレッドのボトルネックの文脈では、Unityが特定した問題に応じて異なるバッチ処理システムとオプションを提供していることを強調する価値があります。以下は、電子書籍でより詳細に説明するいくつかのオプションの簡単な概要です。

  • SRPバッチ処理は、マテリアルデータをGPUメモリに永続的に保存することでCPUオーバーヘッドを削減します。実際のドローコール数を減らすわけではありませんが、各ドローコールを安価にします。
  • GPUインスタンシングは、同じマテリアルを使用する同じメッシュの複数のインスタンスを1つのドローコールにまとめます。
  • 静的バッチ処理は、同じマテリアルを共有する静的(動かない)メッシュを結合し、多くの静的要素を持つレベルデザインで作業する際に利点をもたらすことができます。
  • GPU常駐ドロワーは、類似のGameObjectをグループ化することでCPUオーバーヘッドとドローコールを削減するために自動的にGPUインスタンシングを使用します。
  • 動的バッチ処理は、ランタイムで小さなメッシュを結合し、高いドローコストを持つ古いモバイルデバイスでの利点となる可能性があります。ただし、欠点は頂点変換がリソース集約的である可能性があることです。
  • GPUオクルージョンカリングは、現在のフレームと前のフレームの深度バッファを比較することでオブジェクトの可視性を判断するためにコンピュートシェーダーを使用し、事前に焼き付けたデータを必要とせずにオクルードされたオブジェクトの不要なレンダリングを削減します。

さらに、CPU側では、Camera.layerCullDistancesのような技術を使用して、カメラからの距離に基づいてオブジェクトをカリングすることでレンダースレッドに送信されるオブジェクトの数を減らし、カメラカリング中のCPUボトルネックを緩和するのに役立ちます。

これらは利用可能なオプションの一部に過ぎません。これらのそれぞれには異なる利点と欠点があります。いくつかは特定のプラットフォームに制限されています。プロジェクトはしばしばこれらのシステムのいくつかの組み合わせを使用する必要があり、それを行うためにはそれらを最大限に活用する方法を理解する必要があります。

CPUバウンド:レンダースレッド
レンダースレッドに制限があるシナリオ

CPUバウンド:ワーカースレッド

メインスレッドやレンダースレッド以外のCPUスレッドに制約されるプロジェクトはそれほど一般的ではありません。ただし、プロジェクトがデータ指向技術スタック(DOTS)を使用している場合、特に作業がメインスレッドからワーカースレッドに移動される場合には発生する可能性があります。

上記の画像は、エディターの再生モードでのキャプチャで、CPU上で粒子流体シミュレーションを実行しているDOTSプロジェクトを示しています。

一見成功しているように見えます。ワーカースレッドはBurst-compiledジョブでぎっしり詰まっており、メインスレッドから多くの作業が移動したことを示しています。通常、これは妥当な決定です。

しかし、この場合、メインスレッドのフレーム時間48.14 msとグレーのWaitForJobGroupIDマーカー35.57 msは、すべてがうまくいっていない兆候です。WaitForJobGroupIDは、メインスレッドがワーカースレッドで非同期に実行するジョブをスケジュールしていることを示していますが、ワーカースレッドがそれらのジョブを実行し終える前に結果が必要です。WaitForJobGroupIDの下にある青いプロファイラーマーカーは、メインスレッドが待機中にジョブを実行していることを示しており、ジョブが早く終了するように努めています。

ジョブはBurstコンパイルされていますが、それでも多くの作業を行っています。おそらく、このプロジェクトで使用されている空間クエリ構造は、どの粒子が互いに近いかを迅速に見つけるために最適化するか、より効率的な構造に置き換えるべきです。または、空間クエリジョブをフレームの開始ではなく終了にスケジュールし、結果は次のフレームの開始まで必要ないようにすることができます。おそらく、このプロジェクトはあまりにも多くの粒子をシミュレートしようとしています。ジョブのコードをさらに分析する必要があるため、より細かいプロファイラーマーカーを追加することで、最も遅い部分を特定するのに役立ちます。

あなたのプロジェクトのジョブは、この例のように並列化されていないかもしれません。おそらく、単一のワーカースレッドで実行されている長いジョブが1つだけです。これは問題ありませんが、ジョブがスケジュールされてから完了するまでの時間が、ジョブが実行されるのに十分長いことが条件です。そうでない場合、上のスクリーンショットのように、ジョブが完了するのを待っている間にメインスレッドがストールするのが見えます。

ワーカースレッドのボトルネックに関する一般的な落とし穴

同期ポイントとワーカースレッドのボトルネックの一般的な原因は次のとおりです:

  • Burstコンパイラーによってコンパイルされていないジョブ
  • 複数のワーカースレッドに並列化されるのではなく、単一のワーカースレッドで長時間実行されるジョブ
  • ジョブがスケジュールされるフレーム内のポイントと結果が必要なポイントの間の時間が不十分
  • フレーム内の複数の「同期ポイント」、すべてのジョブが即座に完了する必要がある

CPU 使用プロファイラモジュールのタイムラインビューで フローイベント 機能を使用して、ジョブがスケジュールされるタイミングと、メインスレッドによって結果が期待されるタイミングを調査できます。

効率的な DOTS コードの記述に関する詳細は、DOTS ベストプラクティス ガイドを参照してください。

CPUバウンド:ワーカースレッド
シミュレーションが重く、ワーカースレッドに束縛された DOTS ベースのプロジェクト

GPU バウンド

アプリケーションが GPU バウンドである場合、メインスレッドが Gfx.WaitForPresentOnGfxThread のようなプロファイラマーカーで多くの時間を費やし、レンダースレッドが同時に Gfx.PresentFrame または .WaitForLastPresent. のようなマーカーを表示します。

GPU フレーム時間を取得する最良の方法は、ターゲットプラットフォーム固有の GPU プロファイリングツールを使用することですが、すべてのデバイスが信頼できるデータをキャプチャするのを容易にするわけではありません。

その場合、FrameTimingManager API が役立ち、CPU と GPU の両方で低オーバーヘッドの高レベルのフレーム時間を提供します。

上記のキャプチャは、Vulkan グラフィックス API を使用して Android モバイルフォンで撮影されました。この例の Gfx.PresentFrame で費やされた時間の一部は VSync を待つことに関連しているかもしれませんが、このプロファイラマーカーの極端な長さは、この時間の大部分が GPU が前のフレームのレンダリングを完了するのを待つことに費やされていることを示しています。

このゲームでは、特定のゲームプレイイベントがシェーダーの使用を引き起こし、GPU によってレンダリングされる描画コールの数を 3 倍にしました。GPU パフォーマンスをプロファイリングする際に調査すべき一般的な問題は次のとおりです:

  • アンビエントオクルージョンやブルームのような高価なフルスクリーンのポストプロセスエフェクト
  • 高価なフラグメントシェーダーの原因:
  • シェーダーコード内の分岐ロジック
  • 特にモバイルで、半精度ではなくフルフロート精度を使用すること
  • レジスタの過剰使用、これが GPU のウェーブフロントの占有率に影響を与える
  • 次の原因による透明レンダーキューでのオーバードロー:
  • 非効率的な UI レンダリング
  • 重複または過剰なパーティクルシステムの使用
  • ポストプロセッシングエフェクト
  • 次のような過剰に高い画面解像度:
  • 4Kディスプレイ
  • モバイルデバイスのRetinaディスプレイ
  • 原因によるマイクロ三角形:
  • 密なメッシュジオメトリ
  • レベルオブディテール(LOD)システムの欠如。これはモバイルGPUに特有の問題ですが、PCやコンソールのGPUにも影響を与える可能性があります。
  • キャッシュミスと無駄なGPUメモリ帯域幅の原因:
  • 非圧縮テクスチャ
  • ミップマップなしの高解像度テクスチャ
  • ジオメトリまたはテッセレーションシェーダー。動的シャドウが有効になっている場合、フレームごとに複数回実行される可能性があります。

アプリケーションがGPUに制約されているように見える場合、フレームデバッガーを使用してGPUに送信されるドローコールバッチを理解するための迅速な方法として利用できます。ただし、このツールは特定のGPUタイミング情報を表示することはできず、全体のシーンがどのように構成されているかのみを示します。

GPUボトルネックの原因を調査する最良の方法は、適切なGPUプロファイラーからのGPUキャプチャを調べることです。使用するツールは、ターゲットハードウェアと選択したグラフィックスAPIによって異なります。詳細については、電子書籍のプロファイリングとデバッグツールセクションを参照してください。

GPU バウンド
GPUに制約されたモバイルゲームからのキャプチャ
Unityベストプラクティスガイド
Unity開発者とクリエイターのためのさらなるヒント

Unityベストプラクティスハブからのさらなるベストプラクティスとヒントを見つけてください。業界の専門家やUnityのエンジニア、テクニカルアーティストが作成した30以上のガイドから選択し、Unityのツールセットとシステムを使用して効率的に開発するのに役立ててください。