mcpField
S
scenario-unity-performance

scenario-unity-performance

github.com/scenario-labs開発1.2k269 回インストール1 日前に更新
リポジトリ

scenario-unity-performance について

Unity のパフォーマンスエンジニアリング向けスキルです。エディター分析、開発ビルド、キャプチャでボトルネックを特定し、対象デバイスで変更前後の数値を検証します。スクリプト、描画、メモリ、ロード時の停止、ビルドサイズを扱います。

主な機能

  1. 01予算の設定と基準データの記録
  2. 02開発ビルドのフレームとキャプチャの分析
  3. 03スクリプト、描画、メモリコストの特定
  4. 04停止、GC 割り当て、GPU 負荷の確認
  5. 05修正の検証と性能予算の維持

活用シーン

  1. 01対象デバイスの CPU または GPU ボトルネックの特定
  2. 02ロード、生成、シェーダーコンパイルによる停止の調査
  3. 03フレームごとの割り当て削減とメモリ予算の確認
  4. 04変更前後のフレーム時間とビルドサイズの比較

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/scenario-labs/skills --skill scenario-unity-performance

インストール後、Claude Code で/scenario-unity-performanceを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
critique.md
expert-notes.md
gui-paths.md
procedures.md
sources.md
scripts/
ut_perf.py
scripts/AgentKit/Performance/
PerfAudit.cs
PerfBuild.cs
PerfCapture.cs
PerfFrameDebug.cs
PerfLod.cs
PerfRendering.cs
PerfShaders.cs
scripts/Runtime/Performance/
AgentKit.Performance.Runtime.asmdef
AllocScope.cs
PerfProbe.cs

詳細

Unity のパフォーマンス(パフォーマンスエンジニア)

高度なパフォーマンス改善は、計測に基づく取り組みです。ミリ秒単位の予算を定め、変更前にボトルネックとなるスレッドを特定し、計測した最大のコストから先に解消します。調整より構造の見直しを優先し、開発プレイヤーで変更前後の数値を比較して、すべての修正を検証します。エージェントはこれらをすべてヘッドレスで実施します。反復作業には Editor Play モードの AgentProfile、判定にはバッチモードプレイヤーの PerfProbe、Profiler キャプチャからコストを特定するには PerfCapture、GPU 時間の計測には metal_trace、サイズの確認には PerfBuild を使用します。対象環境は Unity 6000.3.21f1、URP 17.3、macOS Apple Silicon です。ここに記載された関連スキルが利用可能なスキルにない場合は、ユーザーにインストールを依頼してください (npx skills add scenario-labs/skills --skill <name>)。無人で作業する場合は、ツールのスキーマを参考に進め、スキルが不足していることを明記してください。

必須の背景知識: scenario-unity-expert(チャネル、レビューループ、6.3 の落とし穴、ut_* ツールキット)。

方針(専門家として重視する点)

  1. 何がボトルネックかを特定してから着手します。 「このようなプロファイルなら、GPU を高速化しても実行速度は上がりません」(Unity Profiler チーム、Peter Hall、_cV1B2hqXGI [00:05:50])。実例:フレームごとに 1.36 MB のガベージを削除しても、5,946 件の SetPass 呼び出しがボトルネックとなっていたフレーム時間は変わりませんでした。GPU がボトルネックなら GPU 時間の計測が必要です。Frame Debugger では計測できません(Hall [frame 00:09:18])。
  2. 判断するのはプレイヤー、反復するのは Editor です。 基準となるのは、対象デバイスの中で最も性能が低いデバイス上での開発ビルドです(profiling e-book p. 18)。GRD は、200 万インスタンスのストレステストでは 4.4 倍、Editor では 2.07 倍、実ゲームのスタンドアロン版では 1.45 倍でした(Oc6T4hh5gaI [frame 00:12:41])。この環境では、ストレステスト用のフィクスチャで Editor は 1.5 倍から 3.0 倍、プレイヤーは 1.9 倍から 2.8 倍の間で変動しました。予測値は約 1.5 倍です。
  3. GC.Alloc のサンプル時間はコストそのものではありません。 Unity が記録するのはタイムスタンプとサイズだけです(e-book p. 27)。1.01 ms と表示された 54,021 件の割り当てが、19.97 ms の GC.Collect を引き起こしました(Hall [frame 00:21:52])。実例:フレームごとに 27,000 件の割り当てがあり、表示時間は 0.8~1.1 ms でしたが、割り当てを行うコードのコストは 3.8~5.4 ms でした。バイト数と呼び出し回数を数え、コレクションを計測し、ゲームプレイ中のフレームごとの割り当てを 0 B にすることを目指してください。また、プローブ自体も割り当てを発生させないようにします(git-amend ham_w48aRJ4 [00:11:30])。
  4. 6.3 の URP におけるドローコール対策は SRP Batcher + GPU Resident Drawer です。2024 年のアドバイスは Built-in 専用です。 マテリアルの「Enable GPU Instancing」はオフ、静的バッチングはオフ、MaterialPropertyBlock は使用しません(6.3 Manual の表)。BatchRendererGroup Variants が Keep All であり、URP アセットを使うすべてのレンダラーが Forward+ または Deferred+ でない限り、GRD は何もせずに動作を停止します(URP 17.3 のソースで確認)。次に、対象範囲を検証します。Frame Debugger では GRD のドローが「Hybrid Batch Group」と表示されます(GUI エディターのみ)。それ以外の各ドローは、対象から外れたレンダラーです(MPB、LPPV、リアルタイム GI、DOTS 以外のシェーダー、スキン付き。6.3 Manual)。ヘッドレス環境では、Start 後にレンダラーごとの適格性を確認してください。
  5. まずデータレイアウトを見直します。処理を複数フレームに分散してもコストは隠れるだけです。 オブジェクト 200,000 個の場合、マネージャー、構造体、Burst、SoA を使うことで、MonoBehaviour の Update を 27 ms から 0.13 ms に短縮しました。「やっていることは処理を分散しているだけです」(Jason Booth、NAVbI1HIzCE [00:06:38]-[00:18:17])。
  6. ヒッチの原因を修正する前にすべて特定し、マーカーはコードに残します。 ストリーミングシステムとスポーンシステムには、それぞれ独自の ProfilerMarkers とカウンターを持たせます。Deep Profile は、ギャップを埋めている処理を特定する場合にのみ使います(Hall [00:18:39]-[00:19:12])。コンパイルは Shader.CreateGPUProgram として表示されます(トレースした GraphicsStateCollection を読み込み中にウォームアップします。Nicolas Borromeo CmD8MVGkDxQ [00:11:14]-[00:14:35])。この環境では、ウォームアップによって初回使用時のコンパイル 18 件はすべてなくなりましたが、最初のフレームは Mono.JIT によって約 250 ms のままでした。
  7. 常駐メモリは致命的な問題を引き起こしますが、通常、未追跡メモリはそうではありません。 未追跡メモリが 0.75 GB でも、常駐メモリは 140 MB でした(Borromeo [00:32:22])。Editor のメモリ使用量はプレイヤーの約 5 倍です(Uuzd39AjFWQ [00:10:03])。ノート PC では、ページングと VRAM のオーバーフロー自体がカクつきの原因になります。
  8. カウントは正確ですが、時間は変動します[追加、実測]。 共有マシンでは(この環境のロードアベレージは 50~210)、A/B テストを交互に実行し、一貫した差があることを確認してください。

最初に決めること

対象プラットフォームと各ティアの最小仕様デバイス、フレーム予算(デスクトップでは 16.67 ms、モバイルではその 0.65 倍)、 解像度と品質ティア、変更可能な範囲(コードのみ、またはアート、デザイン、手触りも含む)、繰り返し実行できる ベンチマーク(ストリーミングを評価する場合は時間ベースにします。フレームごとに固定ステップで進めると、 速度を上げた後に境界へ早く到達します)[追加]、スクリプトバックエンドとビルドタイプ、実際に計測したマシン(ディスクリート GPU、 電源接続時、プローブ JSON の device)[追加]。デフォルト:PC ティアの URP アセット、60 fps、1920x1080、反復用の開発版 Mono プレイヤー、サイズ確認用の IL2CPP リリース版。

ワークフロー

  1. 予算とベンチマーク。 GATE:ベースライン CSV。
  2. エディターで反復。 ut_perf.profile(render=False でスクリプトを分離)、時間差には ab_profile を使用します。GATE:「エディター、反復」とラベル付けされた数値。
  3. 静的ベースライン。 PerfRendering.Audit(バッチ処理、レンダラーごとの GRD 適用可否、プラットフォームごとの グラフィックスジョブ、Build Profile、スタックトレース、アップロードバッファー、カリング)、lint_scripts、Project Auditor(project_auditor、Major を優先、triage_auditor でフレームごとの項目を保持)、 PerfAudit.AudioImport、bundle_duplicates。GATE:呼び出し頻度に基づいて検出結果をトリアージします。
  4. プレイヤーを計測。 build_player(development=True)、 run_player(..., shot=png, markers=[...])。GATE: draw_calls > 0、PNG を開けること、budget_check。
  5. ボトルネックを特定。 待機マーカーをキャプチャします(capture_player、analyze_capture)。最長フレームと 中央値のフレームを比較します。GPU:metal_trace + metal_gpu_frames(GPU ビジー時間とパスごとの ms。GUI では Rendering Debugger Display Stats)、続いてピクセル負荷のテスト(4K と 1080p、レンダースケール)を行います。GATE: 数値を示してボトルネックを特定します。
  6. 要因を特定。 attribute_spikes(独自のマーカー、GC.Collect、コンパイル、MeshCollider の調理、 同期読み込み、物理演算ステップ)、physics_catchup、memory_budget、density_check、 ブロックごとの割り当て(AllocScope、Is.Not.AllocatingGCMemory())、GRD の適用範囲 (PerfFrameDebug.Capture またはウィンドウ)を確認します。GATE:主なコストを 3 つ特定し、説明できないスパイクがないこと。
  7. 一度に 1 つずつ修正し、まずは構造から。 スクリプト:割り当て、マネージャー、次に Burst。レンダリングの CPU:Material Variants、SRP Batcher シェーダー、EnableGpuResidentDrawer、カメラを 1 つにする、DX12/Vulkan では グラフィックスジョブを分割します。GPU:動的解像度または STP、深度プライミングと透明描画の削減、 三角形がピクセルサイズになる箇所で LOD を使用し、後処理を固定範囲に限定します。シャドウには scenario-unity-rendering-lighting を使用します。スタッター:非同期読み込み、事前ウォームアップ済みプール、読み込み中の PSO ウォームアップ、 ベイク済み MeshCollider、オーディオのロードタイプ、バンドル依存関係の重複をなくします。GATE: compare_line で対象の数値が下がり、ほかの数値が悪化せず、画像の同等性が保たれていることを確認します。
  8. 出荷時の設定。 IL2CPP リリース版を使用し、ストリッピングとコード生成を選択し、使用セットに基づいてバリアントを ストリップします(P15)。フラグは BuildPlayerOptions を通じて設定します(プラットフォームプロファイル間で設定を共有します)。GATE: ビルド成功、サイズの説明がつくこと、スモークテストに問題がないこと。
  9. ガード。 コミットごとに割り当てテストと Performance Testing のサンプルグループを実行し、変更したスクリプトに対して lint を実行します。GATE:テスト成功。

数値

値基準出典
16.67 / 33.33 ms;モバイルでは x 0.6560 / 30 fps でのフレーム予算;熱的な余裕profiling e-book p. 9, 36
0 Bゲームプレイのフレームごとのマネージド割り当て6.3 Manual; e-book p. 66
約 1 ms までインクリメンタル GC の書き込みバリアによるフレームごとのコストe-book p. 69
4.4x / 2.07x / 1.45xGRD の向上:200 万インスタンスのストレステスト / Editor でのゲーム / 同じゲームのスタンドアロン版Oc6T4hh5gaI [frame 00:12:41]
217 / 199 / 176 / 161 fpsDX12 Split / Legacy / 通常の DX12 / Native graphics jobsOc6T4hh5gaI [frame 00:03:52]
256 頂点これを下回ると GPU インスタンシングは非効率です。代わりに結合します6.3 Manual
約 250 ms1 回の PSO 作成による停止Oc6T4hh5gaI [frame 00:04:48]
4 ピクセル呼び出し1 ピクセルの三角形あたり(2x2 クワッド);描画ピクセルあたり 1 三角形を上限とします[追加]CmD8MVGkDxQ [00:27:26]
80 ms 中 48.55 ms開発フレームでのフレームごとの Debug.Logxjsqv8nj0cw [00:10:23]
< 200 / > 400 KBオーディオ:Decompress On Load / Streamingconsole e-book p. 104-105
最大 1 msモバイルでカメラを 1 台追加した場合の CPU 負荷console e-book p. 89
最大 10 sAutoconnect Profiler による起動時間の増加e-book p. 47
RAM の 50% / 70% / 80%メモリ予算:2 GB のスマートフォン / 一般 / 専用デバイスHall; Unity tutorial; e-book
SetPass 5,946 から 33;58-60 から 5.2-6.2 ms3,000 個の球体、MPB の色付けから 8 種類の Material Variants へ、開発プレイヤー観測値
18 から 010 個の PSO をウォームアップした後、初回使用時の Shader.CreateGPUProgram(ロード時に 3.6 ms)観測値
177.9 / 110.5 / 116.3 MBmacOS アプリ:IL2CPP Minimal + speed / IL2CPP High + size / Mono観測値
三角形 14.75 M から 0.26 M(ピクセルあたり 7.1 から 0.12)Mesh LOD 適用後の高密度メッシュ 400 個、ドローコール数は変化なし観測値
フレーム 57.1 ms = Spawn.Burst 26.9 ms + 4 回のコンパイル;次のフレームで物理演算 3 ステップ3,000 回の Instantiate のバーストを、専用マーカーで計測観測値
48-80 ms 対 0 msスポーン時の MeshCollider 調理と、ロード時にジョブで行う Physics.BakeMesh(40 メッシュ)観測値
33 us 対 2.6-3.9 usDebug.Log 1 回あたり、スタックトレース ScriptOnly 対 None(開発プレイヤー)観測値
2.5 対 5.8 ms;ピクセル数 4 倍で 1.2xGPU ビジー時間 対 CPU メイン(Metal System Trace);4K 対 1080p:ピクセル制限ではない観測値
1,944 から 1,002 KBテクスチャを共有する 2 つのバンドル:暗黙のコピーと共有バンドル観測値

品質ゲート

  • 測定可能性: プレイヤーの budget_check が合格(p95 が予算内で、ゲームプレイの引っかかりがないこと)。プレイヤーの gc_check が合格。説明のつかない attribute_spikes が 0。強制的なスパイク以外での physics_steps が最大 1。memory_budget が合格。density_check が合格。PerfRendering.Audit の counts.error が 0 で、すべての GRD の影響が説明されていること。読み込み後の Shader.CreateGPUProgram が 0 件。コードに新たな Major Project Auditor の問題がないこと。IL2CPP ビルドが予算内であること。
  • 視覚: プレイヤーの最終フレーム(-perfShot)と、同じブックマークから取得した変更前後のエディターキャプチャで ut_review.compare を実行し、コンタクトシートを開いていること。空白、一様、またはマゼンタのフレームがないこと。

よくある間違い

間違い発生する現象対策
ボトルネックではない側を最適化するアロケーションは減るが、フレーム時間は変わらないまず原因を分類する(待機マーカー、GPU のビジー状態、SetPass)
Editor の数値や 1 回の実行結果を信頼する「3 倍高速」がプレイヤーでは再現しないプレイヤーの CSV を使い、一貫した符号で ab_profile を実行する
何も描画しないバッチモードのプレイヤーをベンチマークするフレーム時間は 0.3 ms、ドローコールは 0 回PerfProbe の submit と -perfShot を使う
GRD を有効にしても何も変わらないドローコール数が変わらないKeep All、Forward+/Deferred+、静的バッチングの無効化、フォールアウトの修正
オブジェクトごとの色に MaterialPropertyBlock を使うSetPass = ドローコール数になり、StdRender.ApplyShader が支配的になるMaterial Variants を使う。GRD では unity_RendererUserValue を使う
GC.Alloc ms をコストとして読む「アロケーションに 0.5 ms かかる」回数とバイト数、GC.Collect の ms、A/B を確認する
毎フレーム文字列をフォーマットするモニターGC の列が 0 にならないバッファーを事前確保し、1 回だけ書き込む(PerfProbe)
ほとんど実行されない分岐内のクロージャー機能を無効にしてもアロケーションが発生するキャプチャするローカル変数を分岐の中に移動する
スパイクの特定に Deep Profile を使う小さな関数の時間が過大に表示されるマーカーとカウンターをコード内に残し、-perfMarkers を使う
Frame Debugger で GPU をチューニングするタイミングを推測することになるmetal_trace を使う(Windows では PIX、Nsight)
ノート PC でレンダースケールを固定する低温時は問題ないが、高温時や負荷時にヒッチが発生する動的解像度を使う。ピクセル数の上限が固定の場合のみ、STP を固定スケールで使う
ウォームアップで初回フレームのヒッチが解消すると考える依然として 250 ms かかる計測する。JIT は IL2CPP、読み込みは非同期処理を意味する
フレーム分散を解決策にするピークは下がるが、合計時間は変わらないまず未分散の状態を計測し、構造を修正する
実行時に MeshCollider を追加するスポーン時にスパイクが発生し、Physics.BakePhysXCollisionMeshData が実行されるジョブ内で Physics.BakeMesh を使い、Prebake Collision Meshes を有効にする
EditorUserBuildSettings で開発フラグを設定するすべてのプラットフォームプロファイルが変更されるBuildPlayerOptions またはビルドプロファイルアセットを使う
BuildReport の totalSize でサイズを見積もる178 MB のアプリが 1.9 GB と表示されるディスク上の配布フォルダーを確認する
ヒッチ対策として InstantiateAsync を使う後続フレームで 17~83 ms かかるプールを使い、トランジション中に事前ウォームアップする
新しいビルドの初回起動をベンチマークする212 ms と 22 ms の差が出る初回起動の結果を破棄する

承認なしに変更してはいけない項目

ゲームフィール(固定タイムステップ、Maximum Allowed Timestep、アニメーションのタイミング、入力、AI のティック);アートディレクション(テクスチャサイズ、ライト、LOD のしきい値、レンダースケール)は変更前後のキャプチャなしでは変更しないこと;パイプライン、エンジンのバージョン、または ECS の書き換え;ゲームプレイ中の割り当てが 0 B で、A/B 比較で効果が確認できない限り、インクリメンタル GC を無効にしないこと;asyncUploadBufferSize をむやみに増やさないこと(元に戻せません。コンソール電子書籍 p. 31);リリース時に VSync を無効にしないこと;変更前後でベンチマークの経路を変えないこと。

引き継ぎ

  • 受け取るもの: scenario-unity-expert からの数値を含む概要;scenario-unity-architecture および scenario-unity-gameplay からのシステムコード;scenario-unity-rendering-lighting からのパイプラインアセット;scenario-unity-shaders からのシェーダー;scenario-unity-vfx からのエフェクト;scenario-unity-ui からの UI;scenario-unity-pipeline-automation からのバンドルと Addressables。
  • 引き渡すもの: コストを分類して明示したデータ、変更前後の表、CSV、トレース、PNG:バリアントのストリッピングは scenario-unity-shaders へ、ライティング、シャドウ、ポストプロセスの削減は scenario-unity-rendering-lighting へ、オーバードローの予算は scenario-unity-vfx および scenario-unity-ui へ、物理設定は scenario-unity-gameplay へ、重複の修正、CI パフォーマンステスト、ビルドプロファイルは scenario-unity-pipeline-automation へ、デバイスおよび熱の検証は scenario-unity-mobile へ、Web の制限事項は scenario-unity-web へ。

Unity 6.3 に関する注意事項

  • SRP Batcher + GRD + GPU オクルージョン(オクルージョンフラグは 17.3 URP アセットにあります);動的バッチングは 6.5 で非推奨、6.6 で廃止。
  • Split graphics jobs は DX12/Vulkan/コンソールでデフォルトです(Oc6T4hh5gaI [00:03:04]);6.3 URP テンプレートでは Windows はオン/Split、Android はオフ/Native、Metal はオフ/Native です。プラットフォームごとのゲッターは内部仕様です(確認済み)。
  • GraphicsStateCollection は実験的機能で、トレースは開発用プレイヤーでのみ利用できます。WebGPU のトレースは 6.4 で利用可能です。FrameDebuggerUtility は内部仕様です。
  • Profile Analyzer 1.4.0、Memory Profiler 1.1.12、Project Auditor 1.0.2(6.4 から組み込み)、Performance Testing 3.5.0(6.6 でコアに追加)、Profiling Core 1.0.3(ProfilerCounterValue に必要)は、名前を指定して追加するプレインストール済みパッケージです。
  • メッシュ LOD は 6.2 以降です。Physics.BakeMesh(int, bool) は非推奨です。mesh.GetEntityId() を渡してください。6.3 は Boehm(非世代別 GC)を実行します。CoreCLR は今後の対応予定です。

参考資料

コメント