unity-performance について
Unity 開発者向けのパフォーマンス分析・最適化リファレンススキルです。Profiler、Memory Profiler、Frame Debugger を使い、CPU、GPU、メモリ、描画、スクリプトを調べる方法を扱います。
主な機能
- 01Profiler と分析ツールの概要
- 02CPU・GPU・メモリ指標の解説
- 03GC 割り当てとアセットメモリの最適化
- 04スクリプト・物理・描画の最適化
- 05Frame Debugger による描画呼び出しの確認
- 06実機でのプロファイリング手法
活用シーン
- 01対象デバイスでフレーム時間のボトルネックを見つける
- 02ゲーム実行中の GC 割り当てを追跡する
- 03バッチングの中断やオーバードローを調べる
- 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 Profiler | Window > Analysis > Profiler | フレームごとの CPU、GPU、メモリ、レンダリングの各メトリクス |
| Memory Profiler | Package: com.unity.memoryprofiler | 詳細なメモリスナップショット、比較、リーク検出 |
| Frame Debugger | Window > Analysis > Frame Debugger | レンダリングされたフレーム内のすべてのドローコールを検査 |
| Profile Analyzer | Package: 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 負荷、メモリ使用量 |
| UI | Canvas の再構築、レイアウトの再構築、バッチ |
| Video | ビデオプレーヤーのデコード時間 |
プロファイリングのベストプラクティス
- ターゲットデバイスでプロファイリングする -- エディターのオーバーヘッドによって結果が大きく歪みます。必ず実機で検証してください。
- 開発ビルドを使用する -- ビルド設定で
Development BuildとAutoconnect Profilerを有効にします。ビルドの起動時にプロファイラーが自動接続されます。 - ProfilerMarker を使用する -- 重要なコードパスにカスタムマーカーを設定すると、ディーププロファイルのオーバーヘッドなしで正確に測定できます。
- 代表的なシナリオをプロファイリングする -- 最悪条件のシーン(大量の敵、パーティクルエフェクト、複雑な UI)をテストします。
- 変更前後を比較する -- Profile Analyzer を使用して、キャプチャを統計的に比較します。
- フレーム予算を設定する -- 目標を把握しておきます。
- 60 FPS = 1 フレームあたり 16.6 ms
- 120 FPS = 1 フレームあたり 8.3 ms
- 30 FPS = 1 フレームあたり 33.3 ms
- 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)では、フレーム内のすべての描画呼び出しを順に確認できます。
ワークフロー:
- Frame Debugger を開き、Enable をクリックします
- 現在のフレームで Game ビューが停止します
- 左側のパネルで描画呼び出しを順に確認します
- シェーダー、パス、キーワード、レンダーターゲット、メッシュ、マテリアルのプロパティを調べます
- 不要な描画、バッチングの中断、オーバードローを特定します
主な用途:
- 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 | 名前空間 | 用途 |
|---|---|---|
ProfilerMarker | Unity.Profiling | オーバーヘッドゼロのカスタムプロファイラー計測 |
ProfilerRecorder | Unity.Profiling | 実行時にプロファイラーカウンターの値を読み取ります |
Profiler.BeginSample/EndSample | UnityEngine.Profiling | 旧式のカスタムプロファイラーサンプル(開発ビルド以外では除去されます) |
ObjectPool<T> | UnityEngine.Pool | 汎用オブジェクトプーリング |
ListPool<T>/HashSetPool<T> | UnityEngine.Pool | 一時コレクションのプーリング |
NativeArray<T> | Unity.Collections | Jobs/Burst 用のアンマネージドかつ GC 不要な配列 |
NativeList<T> | Unity.Collections | アンマネージドの可変長リスト |
Stopwatch | System.Diagnostics | 高精度な時間計測 |
ShaderVariantCollection | UnityEngine.Rendering | コンパイルによるカクつきを防ぐシェーダーのウォームアップ |
QualitySettings | UnityEngine | 実行時の品質レベル切り替え |
関連スキル
- スクリプティングのパターンとコンポーネントのライフサイクルについては、unity-scripting を参照してください
- レンダリングパイプラインとシェーダーの最適化については、unity-graphics を参照してください
- モバイル/コンソールプラットフォームの最適化については、unity-platforms を参照してください
- ECS、Jobs、Burst(データ指向のパフォーマンス)については、unity-ecs-dots を参照してください
- 物理演算の設定と最適化については、unity-physics を参照してください
- UI のパフォーマンス(Canvas の再構築、バッチング)については、unity-ui を参照してください
追加リソース
- Profiler API の使用方法とワークフローの詳細については、references/profiler-guide.md を参照してください
- メモリ管理の戦略の詳細については、references/memory-optimization.md を参照してください