scenario-unity-performance について
Unity のパフォーマンスエンジニアリング向けスキルです。エディター分析、開発ビルド、キャプチャでボトルネックを特定し、対象デバイスで変更前後の数値を検証します。スクリプト、描画、メモリ、ロード時の停止、ビルドサイズを扱います。
主な機能
- 01予算の設定と基準データの記録
- 02開発ビルドのフレームとキャプチャの分析
- 03スクリプト、描画、メモリコストの特定
- 04停止、GC 割り当て、GPU 負荷の確認
- 05修正の検証と性能予算の維持
活用シーン
- 01対象デバイスの CPU または GPU ボトルネックの特定
- 02ロード、生成、シェーダーコンパイルによる停止の調査
- 03フレームごとの割り当て削減とメモリ予算の確認
- 04変更前後のフレーム時間とビルドサイズの比較
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-unity-performanceインストール後、Claude Code で/scenario-unity-performanceを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdscripts/ut_perf.pyscripts/AgentKit/Performance/PerfAudit.csPerfBuild.csPerfCapture.csPerfFrameDebug.csPerfLod.csPerfRendering.csPerfShaders.csscripts/Runtime/Performance/AgentKit.Performance.Runtime.asmdefAllocScope.csPerfProbe.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_* ツールキット)。
方針(専門家として重視する点)
- 何がボトルネックかを特定してから着手します。 「このようなプロファイルなら、GPU を高速化しても実行速度は上がりません」(Unity Profiler チーム、Peter Hall、_cV1B2hqXGI [00:05:50])。実例:フレームごとに 1.36 MB のガベージを削除しても、5,946 件の SetPass 呼び出しがボトルネックとなっていたフレーム時間は変わりませんでした。GPU がボトルネックなら GPU 時間の計測が必要です。Frame Debugger では計測できません(Hall [frame 00:09:18])。
- 判断するのはプレイヤー、反復するのは 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 倍です。
- 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])。 - 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後にレンダラーごとの適格性を確認してください。 - まずデータレイアウトを見直します。処理を複数フレームに分散してもコストは隠れるだけです。 オブジェクト 200,000 個の場合、マネージャー、構造体、Burst、SoA を使うことで、MonoBehaviour の Update を 27 ms から 0.13 ms に短縮しました。「やっていることは処理を分散しているだけです」(Jason Booth、NAVbI1HIzCE [00:06:38]-[00:18:17])。
- ヒッチの原因を修正する前にすべて特定し、マーカーはコードに残します。 ストリーミングシステムとスポーンシステムには、それぞれ独自の 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 のままでした。 - 常駐メモリは致命的な問題を引き起こしますが、通常、未追跡メモリはそうではありません。 未追跡メモリが 0.75 GB でも、常駐メモリは 140 MB でした(Borromeo [00:32:22])。Editor のメモリ使用量はプレイヤーの約 5 倍です(Uuzd39AjFWQ [00:10:03])。ノート PC では、ページングと VRAM のオーバーフロー自体がカクつきの原因になります。
- カウントは正確ですが、時間は変動します[追加、実測]。 共有マシンでは(この環境のロードアベレージは 50~210)、A/B テストを交互に実行し、一貫した差があることを確認してください。
最初に決めること
対象プラットフォームと各ティアの最小仕様デバイス、フレーム予算(デスクトップでは 16.67 ms、モバイルではその 0.65 倍)、
解像度と品質ティア、変更可能な範囲(コードのみ、またはアート、デザイン、手触りも含む)、繰り返し実行できる
ベンチマーク(ストリーミングを評価する場合は時間ベースにします。フレームごとに固定ステップで進めると、
速度を上げた後に境界へ早く到達します)[追加]、スクリプトバックエンドとビルドタイプ、実際に計測したマシン(ディスクリート GPU、
電源接続時、プローブ JSON の device)[追加]。デフォルト:PC ティアの URP アセット、60 fps、1920x1080、反復用の開発版
Mono プレイヤー、サイズ確認用の IL2CPP リリース版。
ワークフロー
- 予算とベンチマーク。 GATE:ベースライン CSV。
- エディターで反復。
ut_perf.profile(render=Falseでスクリプトを分離)、時間差にはab_profileを使用します。GATE:「エディター、反復」とラベル付けされた数値。 - 静的ベースライン。
PerfRendering.Audit(バッチ処理、レンダラーごとの GRD 適用可否、プラットフォームごとの グラフィックスジョブ、Build Profile、スタックトレース、アップロードバッファー、カリング)、lint_scripts、Project Auditor(project_auditor、Major を優先、triage_auditorでフレームごとの項目を保持)、PerfAudit.AudioImport、bundle_duplicates。GATE:呼び出し頻度に基づいて検出結果をトリアージします。 - プレイヤーを計測。
build_player(development=True)、run_player(..., shot=png, markers=[...])。GATE:draw_calls> 0、PNG を開けること、budget_check。 - ボトルネックを特定。 待機マーカーをキャプチャします(
capture_player、analyze_capture)。最長フレームと 中央値のフレームを比較します。GPU:metal_trace+metal_gpu_frames(GPU ビジー時間とパスごとの ms。GUI では Rendering Debugger Display Stats)、続いてピクセル負荷のテスト(4K と 1080p、レンダースケール)を行います。GATE: 数値を示してボトルネックを特定します。 - 要因を特定。
attribute_spikes(独自のマーカー、GC.Collect、コンパイル、MeshCollider の調理、 同期読み込み、物理演算ステップ)、physics_catchup、memory_budget、density_check、 ブロックごとの割り当て(AllocScope、Is.Not.AllocatingGCMemory())、GRD の適用範囲 (PerfFrameDebug.Captureまたはウィンドウ)を確認します。GATE:主なコストを 3 つ特定し、説明できないスパイクがないこと。 - 一度に 1 つずつ修正し、まずは構造から。 スクリプト:割り当て、マネージャー、次に Burst。レンダリングの
CPU:Material Variants、SRP Batcher シェーダー、
EnableGpuResidentDrawer、カメラを 1 つにする、DX12/Vulkan では グラフィックスジョブを分割します。GPU:動的解像度または STP、深度プライミングと透明描画の削減、 三角形がピクセルサイズになる箇所で LOD を使用し、後処理を固定範囲に限定します。シャドウには scenario-unity-rendering-lighting を使用します。スタッター:非同期読み込み、事前ウォームアップ済みプール、読み込み中の PSO ウォームアップ、 ベイク済み MeshCollider、オーディオのロードタイプ、バンドル依存関係の重複をなくします。GATE:compare_lineで対象の数値が下がり、ほかの数値が悪化せず、画像の同等性が保たれていることを確認します。 - 出荷時の設定。 IL2CPP リリース版を使用し、ストリッピングとコード生成を選択し、使用セットに基づいてバリアントを
ストリップします(P15)。フラグは
BuildPlayerOptionsを通じて設定します(プラットフォームプロファイル間で設定を共有します)。GATE: ビルド成功、サイズの説明がつくこと、スモークテストに問題がないこと。 - ガード。 コミットごとに割り当てテストと Performance Testing のサンプルグループを実行し、変更したスクリプトに対して lint を実行します。GATE:テスト成功。
数値
| 値 | 基準 | 出典 |
|---|---|---|
| 16.67 / 33.33 ms;モバイルでは x 0.65 | 60 / 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.45x | GRD の向上:200 万インスタンスのストレステスト / Editor でのゲーム / 同じゲームのスタンドアロン版 | Oc6T4hh5gaI [frame 00:12:41] |
| 217 / 199 / 176 / 161 fps | DX12 Split / Legacy / 通常の DX12 / Native graphics jobs | Oc6T4hh5gaI [frame 00:03:52] |
| 256 頂点 | これを下回ると GPU インスタンシングは非効率です。代わりに結合します | 6.3 Manual |
| 約 250 ms | 1 回の PSO 作成による停止 | Oc6T4hh5gaI [frame 00:04:48] |
| 4 ピクセル呼び出し | 1 ピクセルの三角形あたり(2x2 クワッド);描画ピクセルあたり 1 三角形を上限とします[追加] | CmD8MVGkDxQ [00:27:26] |
| 80 ms 中 48.55 ms | 開発フレームでのフレームごとの Debug.Log | xjsqv8nj0cw [00:10:23] |
| < 200 / > 400 KB | オーディオ:Decompress On Load / Streaming | console e-book p. 104-105 |
| 最大 1 ms | モバイルでカメラを 1 台追加した場合の CPU 負荷 | console e-book p. 89 |
| 最大 10 s | Autoconnect 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 ms | 3,000 個の球体、MPB の色付けから 8 種類の Material Variants へ、開発プレイヤー | 観測値 |
| 18 から 0 | 10 個の PSO をウォームアップした後、初回使用時の Shader.CreateGPUProgram(ロード時に 3.6 ms) | 観測値 |
| 177.9 / 110.5 / 116.3 MB | macOS アプリ: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 us | Debug.Log 1 回あたり、スタックトレース ScriptOnly 対 None(開発プレイヤー) | 観測値 |
| 2.5 対 5.8 ms;ピクセル数 4 倍で 1.2x | GPU ビジー時間 対 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 は今後の対応予定です。
参考資料
references/procedures.md:完全な呼び出し手順を含む 18 の手順。それぞれにライブテストと記録された結果があります。references/expert-notes.md:タイムスタンプ、意見の相違、判断条件を含む、エキスパート別の原則。references/critique.md:パフォーマンス改善を評価するための基準。references/gui-paths.md:Profiler、Frame Debugger、Rendering Debugger、Project Auditor、設定のパス。references/sources.md:認証情報、URL、最適なタイムスタンプ、改訂履歴を含むすべてのソース。scripts/ut_perf.py(ランナー側)、scripts/AgentKit/Performance/*.cs(Editor ジョブ)、scripts/Runtime/Performance/*.cs(PerfProbe、AllocScope)。