mcpField
U
unity-performance

unity-performance

github.com/nice-wolf-studio開発4090 回インストール7 週間前に更新
リポジトリ

unity-performance について

Unity 開発者向けのパフォーマンス分析・最適化リファレンススキルです。Profiler、Memory Profiler、Frame Debugger を使い、CPU、GPU、メモリ、描画、スクリプトを調べる方法を扱います。

主な機能

  1. 01Profiler と分析ツールの概要
  2. 02CPU・GPU・メモリ指標の解説
  3. 03GC 割り当てとアセットメモリの最適化
  4. 04スクリプト・物理・描画の最適化
  5. 05Frame Debugger による描画呼び出しの確認
  6. 06実機でのプロファイリング手法

活用シーン

  1. 01対象デバイスでフレーム時間のボトルネックを見つける
  2. 02ゲーム実行中の GC 割り当てを追跡する
  3. 03バッチングの中断やオーバードローを調べる
  4. 04テクスチャ、メッシュ、音声のメモリ使用量を減らす

インストール

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

$npx skills add https://github.com/nice-wolf-studio/unity-claude-skills --skill unity-performance

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

同梱ファイルreferences/memory-optimization.mdreferences/profiler-guide.md

詳細

Unity のパフォーマンスプロファイリングと最適化

Unity 6.3 LTS(6000.3)の公式ドキュメントに基づいています

主要ツールの概要

Unity には、プロファイリングと最適化のための組み込みツールやパッケージベースのツールがいくつか用意されています。

ツールアクセス方法目的
Unity ProfilerWindow > Analysis > Profilerフレームごとの CPU、GPU、メモリ、レンダリングの各メトリクス
Memory ProfilerPackage: com.unity.memoryprofiler詳細なメモリスナップショット、比較、リーク検出
Frame DebuggerWindow > Analysis > Frame Debuggerレンダリングされたフレーム内のすべてのドローコールを検査
Profile AnalyzerPackage: com.unity.performance.profile-analyzer多数の Profiler フレームにわたる統計分析

Unity Profiler

Profiler はフレームごとのパフォーマンスデータを記録します。Editor 内で実行するか、ターゲットデバイス上の Development Build に接続して使用します。

CPU モジュール

CPU モジュールは、パフォーマンスのボトルネックを見つけるための主要なツールです。

ビュー:

  • Timeline view -- すべてのスレッドを横並びで表示し、x 軸に時間を示します。スレッドの競合、ジョブシステムの使用状況、フレーム時間の使われ方の確認に最適です。
  • Hierarchy view -- サンプルをコールツリーに集約します。Total %、Self %、Calls、GC Alloc、Time の各列を表示します。最も処理コストの高い関数を見つけるのに最適です。

主なスレッド:

  • Main Thread -- ゲームロジック、スクリプト、アニメーション、UI レイアウト
  • Render Thread -- GPU にドローコールを送信します(SRP コマンドバッファの実行)
  • Job Worker スレッド -- C# Jobs、Burst コンパイルされたワークロード、物理シミュレーション

Profiler マーカー:

  • 組み込みマーカー(例:PlayerLoop、Update.ScriptRunBehaviourUpdate、Physics.Simulate)
  • Unity.Profiling.ProfilerMarker を使用したカスタムマーカー(references/profiler-guide.md を参照)

Deep Profiling:

  • すべての C# メソッド呼び出しを自動で計測します
  • 警告:オーバーヘッドが 10~100 倍増加するため、結果は実際のパフォーマンスを反映しません
  • 代わりに ProfilerMarker を使用して、対象を絞った計測を行ってください

GC 割り当てのコールスタック:

  • Profiler のツールバーで有効にすると、管理対象メモリの割り当て元を確認できます
  • オーバーヘッドは中程度ですが、GC スパイクの原因を特定するうえで非常に役立ちます
GPU モジュール
  • GPU のフレーム時間をレンダリングパスごとに分類して表示します
  • シェーダーの複雑さやフィルレートの問題を特定します
  • オーバードローや処理コストの高いフラグメントシェーダーの診断に役立ちます
  • 注:GPU プロファイリングはすべてのプラットフォームで利用できるわけではありません(プラットフォームのドキュメントを確認してください)
  • モバイルでは、プラットフォーム固有の GPU プロファイラー(Xcode GPU Debugger、RenderDoc、Arm Mobile Studio)を使用してください
メモリモジュール

フレームごとのメモリ概要を表示します。

  • Managed memory -- C# ヒープ(Mono/IL2CPP)、GC 割り当て
  • Native memory -- テクスチャ、メッシュ、オーディオクリップ、レンダーターゲット、ネイティブコンテナー
  • Total Reserved vs Used -- Unity が OS から確保したメモリ量と、実際に使用しているメモリ量
  • GC Allocated in Frame -- 重要なメトリクスです。ゲームプレイ中にゼロ以外の値がある場合、割り当て負荷が発生していることを示します
その他のモジュール
モジュール主なメトリクス
Renderingバッチ、SetPass 呼び出し、三角形、頂点、シャドウキャスター
Physicsアクティブ/スリープ中の Rigidbody、接触、ブロードフェーズのペア
Audio再生中のソース、DSP 負荷、メモリ使用量
UICanvas の再構築、レイアウトの再構築、バッチ
Videoビデオプレーヤーのデコード時間

プロファイリングのベストプラクティス

  1. ターゲットデバイスでプロファイリングする -- エディターのオーバーヘッドによって結果が大きく歪みます。必ず実機で検証してください。
  2. 開発ビルドを使用する -- ビルド設定で Development Build と Autoconnect Profiler を有効にします。ビルドの起動時にプロファイラーが自動接続されます。
  3. ProfilerMarker を使用する -- 重要なコードパスにカスタムマーカーを設定すると、ディーププロファイルのオーバーヘッドなしで正確に測定できます。
  4. 代表的なシナリオをプロファイリングする -- 最悪条件のシーン(大量の敵、パーティクルエフェクト、複雑な UI)をテストします。
  5. 変更前後を比較する -- Profile Analyzer を使用して、キャプチャを統計的に比較します。
  6. フレーム予算を設定する -- 目標を把握しておきます。
    • 60 FPS = 1 フレームあたり 16.6 ms
    • 120 FPS = 1 フレームあたり 8.3 ms
    • 30 FPS = 1 フレームあたり 33.3 ms
  7. CPU のプロファイリング中は VSync を無効にする -- VSync によって CPU の余力が見えなくなります。

メモリの最適化

避けるべき GC 割り当てパターン

マネージド割り当てはすべて、GC の負荷につながります。ホットパス(Update、FixedUpdate、LateUpdate)では、割り当てをゼロにすることを目指します。

パターン問題対処法
文字列の連結(+)毎回新しい文字列を生成しますStringBuilder または string.Create を使用します
LINQ クエリイテレーターの割り当てやクロージャのボクシングが発生します手動の for/foreach ループを使用します
ローカル変数をキャプチャするラムダクロージャコンパイラーがクラスの割り当てを生成しますデリゲートをキャッシュするか、静的ラムダを使用します
ボクシング(int から object)マネージドヒープに割り当てられますジェネリック API を使用し、object パラメーターを避けます
ジェネリックではない IEnumerable に対する foreach列挙子のボクシングが発生します型付きコレクションまたは for ループを使用します
params 配列呼び出しのたびに配列が割り当てられますオーバーロードを用意するか、Span<T> を使用します
値型に対する ToString()文字列が割り当てられますホットパスではキャッシュするか、使用を避けます
アセットメモリ

テクスチャ(多くの場合、最大のメモリ消費要因です):

  • モバイルでは ASTC 圧縮、デスクトップでは BC7/DXT を使用します
  • ミップマップストリーミング(Texture.streamingMipmaps)を有効にして、VRAM 使用量を制限します
  • 品質に問題がない範囲で最大テクスチャサイズを小さくします
  • テクスチャアトラスを使用して、ドローコールとメモリの断片化を減らします

メッシュ:

  • インポート設定でメッシュ圧縮を有効にします
  • LOD グループを使用して、距離に応じて頂点数を減らします
  • インポート設定で未使用の頂点チャンネル(接線、カラー)を削除します

オーディオ:

  • Decompress on Load -- 再生は高速ですが、メモリ使用量は大きくなります(短い SFX のみ)
  • Compressed in Memory -- メモリ使用量は小さく、CPU 負荷は中程度です(中程度の長さのクリップ)
  • Streaming -- メモリ使用量は最小限で、継続的な I/O が発生します(音楽、環境音)
  • 長さと使用頻度に応じて、クリップごとに Load Type を設定します

アンロード:

  • Resources.UnloadUnusedAssets() -- 参照されていないアセットを解放します(時間がかかるため、ロード画面で使用します)
  • Addressables:Addressables.Release() を使用して参照カウントを減らし、ゼロになったらアンロードします
オブジェクトプーリング

頻繁に生成・破棄されるオブジェクトを再利用して、GC の負荷とインスタンス化のコストを避けます。

using UnityEngine.Pool;

public class BulletSpawner : MonoBehaviour
{
    [SerializeField] private Bullet bulletPrefab;
    private ObjectPool<Bullet> _pool;

    private void Awake()
    {
        _pool = new ObjectPool<Bullet>(
            createFunc: () => Instantiate(bulletPrefab),
            actionOnGet: b => b.gameObject.SetActive(true),
            actionOnRelease: b => b.gameObject.SetActive(false),
            actionOnDestroy: b => Destroy(b.gameObject),
            defaultCapacity: 20,
            maxSize: 100
        );
    }

    public Bullet GetBullet() => _pool.Get();
    public void ReturnBullet(Bullet b) => _pool.Release(b);
}

プーリングパターンの詳細については、unity-scripting も参照してください。


CPU の最適化

スクリプティング
  • GetComponent の結果をキャッシュする -- Awake/Start で呼び出し、フィールドに保存します
  • CompareTag("Tag") を使用する -- gameObject.tag による文字列の割り当てを回避します
  • ホットパスで Find メソッドを使用しない -- Find、FindObjectOfType、FindObjectsByType は処理コストが高いため、参照をキャッシュします
  • C# Jobs + Burst を使用する -- データ量の多い処理(空間クエリ、AI、経路探索)に使用します。unity-ecs-dots を参照してください。
  • Awaitable を使った非同期操作 -- シーンのロード、アセットのロード、Web リクエストをノンブロッキングで実行します
// Cache component references
private Rigidbody _rb;
private void Awake() => _rb = GetComponent<Rigidbody>();

// Use CompareTag instead of string comparison
if (other.CompareTag("Enemy")) { /* ... */ }
物理演算
  • コライダーの簡素化 -- MeshCollider よりもプリミティブコライダー(Box、Sphere、Capsule)を使用します
  • レイヤーベースの衝突マトリックス -- Project Settings > Physics で不要な衝突ペアを無効にします
  • FixedUpdate のタイムステップ -- デフォルトは 0.02s(50Hz)です。負荷の低いゲームでは 0.03~0.04s に増やします。
  • Rigidbody のスリープ -- オブジェクトがスリープ状態になるようにします。アイドル状態のオブジェクトに対する AddForce の常時実行は避けます。
  • アロケーションを発生させないクエリ -- Physics.RaycastNonAlloc、Physics.OverlapSphereNonAlloc を使用します
レンダリング
  • SRP Batcher -- URP/HDRP ではデフォルトで有効です。対応シェーダーの SetPass 呼び出しのオーバーヘッドを削減します。
  • 静的バッチング -- 動かないオブジェクトを Batching Static に設定して、ビルド時にメッシュを統合します。
  • GPU インスタンシング -- 同一オブジェクト(木、草、破片など)が多数ある場合は、マテリアルで有効にします。
  • 動的バッチング -- 小さなメッシュを実行時にバッチ化します(SRP では効果が限定的なため、GPU インスタンシングを推奨します)。
  • オクルージョンカリング -- オクルージョンデータをベイクし、壁の背後にあるオブジェクトのレンダリングをスキップします。
  • LOD グループ -- 距離に応じてジオメトリの複雑さを軽減します。
  • シェーダーバリアント -- キーワードの組み合わせを最小限に抑え、Player Settings で未使用のバリアントをストリップします。

フレームデバッガー

フレームデバッガー(Window > Analysis > Frame Debugger)では、フレーム内のすべての描画呼び出しを順に確認できます。

ワークフロー:

  1. Frame Debugger を開き、Enable をクリックします
  2. 現在のフレームで Game ビューが停止します
  3. 左側のパネルで描画呼び出しを順に確認します
  4. シェーダー、パス、キーワード、レンダーターゲット、メッシュ、マテリアルのプロパティを調べます
  5. 不要な描画、バッチングの中断、オーバードローを特定します

主な用途:

  • SRP Batcher/バッチングの効果を確認する
  • バッチングが中断される理由を見つける(異なるマテリアル、シェーダーキーワード、レンダーステートの変更)
  • シャドウパスとそのコストを調べる
  • 透明オブジェクトのソート問題をデバッグする
  • レンダーターゲットの切り替えを確認する(切り替えごとにオーバーヘッドが発生します)
  • フルスクリーンのポストプロセスパス数を確認する

IL2CPP とマネージドコードのパフォーマンス

Unity 6 では、ほとんどのプラットフォームで IL2CPP がデフォルトで使用されます。パフォーマンスに関する主な考慮事項は次のとおりです。

  • IL2CPP の事前コンパイル により C# が C++ に変換され、Mono JIT よりも最適化が向上します
  • コードストリッピング により未使用のコードが削除され、ビルドサイズが縮小し、起動が高速化します
  • ジェネリック共有 -- IL2CPP は参照型のジェネリックではコードを共有しますが、値型のジェネリックでは個別のコードを生成します。値型のジェネリックを過剰にインスタンス化すると、バイナリサイズが増加します。
  • 仮想メソッド呼び出し -- IL2CPP は vtable ディスパッチを使用します。クラスやメソッドに sealed を指定すると、非仮想化とインライン化が可能になります
  • 構造体のレイアウト -- ネイティブコードに渡すデータには [StructLayout(LayoutKind.Sequential)] を使用します。効率的に受け渡せるよう、構造体は小さく(16 バイト未満に)保ちます。
// sealed enables devirtualization -- IL2CPP can inline the call
public sealed class FastEnemy : EnemyBase
{
    public override void Think()
    {
        // This can be inlined by IL2CPP when called through a typed reference
    }
}

品質設定とアダプティブパフォーマンス

実行時の品質切り替え
// Switch quality level at runtime (e.g., from settings menu)
QualitySettings.SetQualityLevel(qualityIndex, applyExpensiveChanges: true);

// Adjust individual settings
QualitySettings.shadows = ShadowQuality.Disable;
QualitySettings.vSyncCount = 0;
Application.targetFrameRate = 60;
アダプティブパフォーマンス(モバイル)

Adaptive Performance パッケージ(com.unity.adaptiveperformance)は、デバイスの熱状態とパフォーマンス指標に基づいて品質を動的に調整します。

  • デバイスが過熱すると、解像度、LOD バイアス、シャドウ品質を自動的に下げます
  • カスタムのスケーリングロジック向けに IAdaptivePerformance インターフェースを提供します
  • Samsung(GameSDK)とユニバーサルプロバイダーをサポートします

一般的なパフォーマンスパターン

非同期シーン読み込み
using UnityEngine.SceneManagement;

public async Awaitable LoadSceneAsync(string sceneName)
{
    AsyncOperation op = SceneManager.LoadSceneAsync(sceneName);
    op.allowSceneActivation = false;

    while (op.progress < 0.9f)
    {
        // Update loading bar: op.progress / 0.9f
        await Awaitable.NextFrameAsync();
    }
    op.allowSceneActivation = true;
}
フレーム間での処理分散
// Process a large list over multiple frames
private IEnumerator ProcessChunked<T>(List<T> items, int chunkSize, System.Action<T> process)
{
    for (int i = 0; i < items.Count; i++)
    {
        process(items[i]);
        if ((i + 1) % chunkSize == 0)
            yield return null; // Resume next frame
    }
}
フレーム予算の把握
using System.Diagnostics;

private readonly Stopwatch _sw = new();
private const float BUDGET_MS = 4f; // Portion of frame budget

private void Update()
{
    _sw.Restart();
    while (_workQueue.Count > 0 && _sw.Elapsed.TotalMilliseconds < BUDGET_MS)
    {
        ProcessNextItem(_workQueue.Dequeue());
    }
}
Additive Scene によるレベルストリーミング
using UnityEngine.SceneManagement;

// Load adjacent area additively without blocking
public async Awaitable StreamArea(string sceneName)
{
    AsyncOperation op = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
    while (!op.isDone)
        await Awaitable.NextFrameAsync();
}

// Unload area when player moves away
public async Awaitable UnloadArea(string sceneName)
{
    AsyncOperation op = SceneManager.UnloadSceneAsync(sceneName);
    while (!op.isDone)
        await Awaitable.NextFrameAsync();
    // Reclaim memory from unloaded assets
    Resources.UnloadUnusedAssets();
}
ProfilerMarker によるカスタム計測
using Unity.Profiling;

public class AISystem : MonoBehaviour
{
    private static readonly ProfilerMarker s_AIUpdate = new("AISystem.Update");
    private static readonly ProfilerMarker s_Pathfind = new("AISystem.Pathfind");

    private void Update()
    {
        using (s_AIUpdate.Auto())
        {
            foreach (var agent in _agents)
            {
                using (s_Pathfind.Auto())
                {
                    agent.RecalculatePath();
                }
            }
        }
    }
}

よくあるボトルネックと対処法

症状主な原因対処法
FPS が低く、メインスレッドの CPU 使用率が高いUpdate にロジックが集中しすぎているProfiler で計測し、最適化するか、ジョブ化するか、複数フレームに分散します
GC スパイク(フレームの引っかかり)負荷の高い処理でのメモリ割り当てオブジェクトをプールし、キャッシュし、Update での LINQ/文字列連結を避けます
ドローコール/SetPass コールが多いバッチングされていない、またはマテリアルが多すぎるSRP Batcher を有効にし、GPU インスタンシングを使用し、テクスチャアトラスを作成します
メモリが増え続けるアセットのリーク、Addressables の解放漏れResources.UnloadUnusedAssets() を呼び出し、Addressable の参照カウントを確認します
物理演算の遅延アクティブなコライダーまたは接触が多すぎるコライダーを単純化し、衝突マトリックスのレイヤーを使用し、タイムステップを増やします
シェーダーのコンパイルによるカクつきシェーダーバリアントが必要時にコンパイルされるシェーダーのウォームアップ(ShaderVariantCollection.WarmUp())を使用し、バリアント数を減らします
ロード時間が長いアセットの同期読み込みAddressables.LoadAssetAsync または Resources.LoadAsync を使用します
レンダースレッドがボトルネックドローコールまたは GPU コマンドが多すぎるバッチ数を減らし、シェーダーを単純化し、解像度を下げます

アンチパターン

内容問題点対処法
本番環境での Debug.Log文字列のフォーマット+マネージドメモリの割り当て+I/O[Conditional("UNITY_EDITOR")] を使用するか、スクリプティング定義で除去します
Update での Camera.main呼び出すたびに内部で FindGameObjectWithTag が実行されますAwake または Start でキャッシュします
毎フレームの Instantiate/Destroy呼び出しごとの GC 割り当て+ネイティブ側のオーバーヘッドObjectPool<T> を使用します
Update での GetComponent<T>()毎フレーム、検索コストが発生しますAwake/Start でキャッシュします
ゲーム UI に OnGUI を使用IMGUI は毎フレーム再描画され、GC 負荷が大きくなりますUI Toolkit または uGUI(Canvas)を使用します
パフォーマンステストでの Deep Profileオーバーヘッドが 10~100 倍になり、結果が無効になります対象を絞った計測には ProfilerMarker を使用します
FixedUpdate でのメモリ割り当て物理演算の頻度(デフォルトで 50Hz)で実行されるため、GC の影響が増幅されます事前に割り当て、プールし、NativeArray を使用します
SendMessage/BroadcastMessageリフレクションベースで、メモリを割り当てます直接メソッド呼び出し、イベント、または UnityEvent を使用します

主要 API クイックリファレンス

API名前空間用途
ProfilerMarkerUnity.Profilingオーバーヘッドゼロのカスタムプロファイラー計測
ProfilerRecorderUnity.Profiling実行時にプロファイラーカウンターの値を読み取ります
Profiler.BeginSample/EndSampleUnityEngine.Profiling旧式のカスタムプロファイラーサンプル(開発ビルド以外では除去されます)
ObjectPool<T>UnityEngine.Pool汎用オブジェクトプーリング
ListPool<T>/HashSetPool<T>UnityEngine.Pool一時コレクションのプーリング
NativeArray<T>Unity.CollectionsJobs/Burst 用のアンマネージドかつ GC 不要な配列
NativeList<T>Unity.Collectionsアンマネージドの可変長リスト
StopwatchSystem.Diagnostics高精度な時間計測
ShaderVariantCollectionUnityEngine.Renderingコンパイルによるカクつきを防ぐシェーダーのウォームアップ
QualitySettingsUnityEngine実行時の品質レベル切り替え

関連スキル

  • スクリプティングのパターンとコンポーネントのライフサイクルについては、unity-scripting を参照してください
  • レンダリングパイプラインとシェーダーの最適化については、unity-graphics を参照してください
  • モバイル/コンソールプラットフォームの最適化については、unity-platforms を参照してください
  • ECS、Jobs、Burst(データ指向のパフォーマンス)については、unity-ecs-dots を参照してください
  • 物理演算の設定と最適化については、unity-physics を参照してください
  • UI のパフォーマンス(Canvas の再構築、バッチング)については、unity-ui を参照してください

追加リソース

コメント