Unity & iOS メモリ構造について
- Unity Addressables 最適化ガイド
- Unity Profiler 最適化
- Unity モバイル最適化実践ガイド - Profiler からメモリ構造まで
- Unity & iOS メモリ構造について
iOS メモリ構造
物理メモリ (RAM)
- 物理的にはこれ以上増やせない。
- この上限を超えて割り当てないこと。
- 物理メモリ使用量 != アプリの VM 割り当て量
アプリは物理メモリを直接認識・直接使用しない。
アプリのメモリ割り当ては VM (Virtual Memory) 上で行われる。
仮想メモリ (VM - Virtual Memory)
- アプリは物理メモリを直接使わない。
- 割り当ては VM で行われる。
- ページ単位に分割される (4KB / 16KB)。
- 各ページは物理メモリへマッピングされる。
- 通常、ページは Clean か Dirty の状態を取る。
- エンジンが新規ページを Reserve し、OS に PM への commit を要求する。
- Unity ではこれを
Total **Committed** Memoryと呼ぶ。 - commit 要求した Reserve ページでも、実際に使わなければ PM に commit されない場合がある。
- 物理メモリ使用量 != VM 使用量
- Resident memory: 318.0 MB, Total allocated: 1.76 GB
例: 1.78GB 割り当てても、実使用は約 380MB。
VM の利点
- PM と VM 間で最適化が行われる。
- 単純化: アプリは物理メモリ層の最適化を直接意識しなくてよい。
- VM 使用量が大きくても、物理メモリ使用量は小さい場合がある。
- 本当に重要なのは 物理メモリをどれだけ使っているか。
メモリフットプリント (Memory Footprint)
- メモリフットプリントはアプリの実質占有サイズを意味する。
Resident 比率が高い割り当て領域の合算。
- Resident Memory: 割り当てメモリのうち、実際に物理メモリに常駐している部分。
- 例: 仮想メモリ 500MB 割り当て、Resident 比率 10%。
物理メモリ常駐 50MB + 仮想メモリ上のみ 450MB。
- 一般的にアプリメモリプロファイルは Dirty / Compressed / Clean セグメントで構成される。
- Dirty: ヒープ割り当て、変更されたフレームワーク領域、使用中 Graphics API リソース (Metal)。
アプリが書き込んだメモリ。デコード済み画像バッファも含まれる場合がある。
- Dirty Compressed: ほとんどアクセスされない Dirty ページ。
ディスクスワップ可能。未アクセスページを圧縮して実効空間を増やし、アクセス時に解凍する。
1 2
ディスクスワップとは? 物理メモリが不足した時に、ディスク領域を一時的にメモリのように使う仕組み。
- Clean: マップ済みファイル、読み取り専用フレームワーク、アプリバイナリ (静的コード)。物理メモリからいつでも外せる領域で、常駐率は低め。
- Dirty と Dirty Compressed はメモリフットプリントであり、常駐率が高い。
- Clean は常駐率が低い。
まとめ
- 現在使っている物理メモリ全体 (Resident) がそのままフットプリントでない理由は?
Clean の Resident はいつでも解放可能だから。
Dirty の Resident は解放しにくい。 - Dirty は必ず必要な最小物理メモリを意味する。
実質的な物理メモリ制約は Dirty 基準なので、Dirty が実質フットプリントとなる。
- Dirty メモリが最優先の最適化対象。
例: 動的割り当て。
ただし Clean が過大だとスワップ頻度が増え、オーバーヘッドで性能悪化する場合がある。
Dirty メモリを最優先で最適化すること。
Clean メモリ由来のスワップが頻発してもオーバーヘッドになる。
iOS のメモリ制限
- Xcode Debugger に表示されるメモリ使用量は Dirty メモリ使用量。
- 例の図では仮想メモリ割り当てが 2GB 超。
- あるアプリの Dirty が増えるとどうなるか?
アプリの Clean 常駐メモリを解放して Dirty 用の物理メモリを確保。
他のバックグラウンドプロセスのメモリ使用も削減される。
- アプリが使える最大メモリは Dirty 基準で計算される。
Unity で何がメモリフットプリントを作るか?
- Unity Native Memory
Unity C++ レイヤー。C# で
Texture2Dをロードすると C++ 側Texture2Dオブジェクトもロードされる。
多くは Dirty メモリ。1 2 3
C# を使っているのに C++ が出てくる理由 Unity は .NET VM 上で動く C++ エンジン。 ネイティブコアは C++、それを制御するのが .NET/C# スクリプト。
- Graphics
モバイルでは GPU/CPU がメモリを共有する (Unified Memory)。
Texture や Shader などの Graphics リソース (Metal リソース)。
Graphics ドライバ。
多くは Dirty メモリ。 - Native Plugin
コードバイナリは Clean メモリ。
Native Plugin の ランタイム割り当て は Dirty メモリ。 - Unity Managed Memory
.NET VM が制御するメモリ。
Unity C# スクリプト層のメモリ。
動的割り当ての多くは Dirty メモリ。
- バイナリ/ネイティブプラグインバイナリ -> Clean メモリ
- それ以外 -> 主に Dirty メモリ
Unity メモリ構造
ネイティブと管理スクリプト
- Unity は .NET 仮想マシンを使う C++ エンジン。
- 2 つのレイヤーがある。
ネイティブコード C++
管理スクリプト .NET/C# - アセットをロードすると、C# 側と C++ 側の両方のメモリとして見えることが多い。
ゲームオブジェクトのバインディング
UnityEngine.Objectを継承した .NET オブジェクト- C++ ネイティブインスタンスとリンクされる
メモリ領域
- Managed Memory: 自動管理される領域。
- Native Memory: エンジン C++ レイヤーが使う領域。
C# Unmanaged Memory: GC 管理対象外の C# メモリ -> Job System / Burst Compiler などで使用。
- 例: フォントアセット
- ネイティブ 342.5 KB
- 管理スクリプト 32 B
Managed Memory
- スクリプティングバックエンド VM により割り当て/制御される領域。
- Managed Heap: 全 C# 割り当て (動的割り当て)。
- C# Scripting Stack: ローカル変数。
- Native VM memory: VM 本体、スクリプティング内部、リフレクション/ジェネリクス用メタデータ領域。
- 割り当ては GC (Garbage Collection) により管理/整理される。
- 管理対象割り当ては
GC AllocationまたはGC.Allocとして表示される。
Managed Memory: 実際の動作
- メモリプール確保 -> リージョン内で近いサイズのオブジェクト用ブロックに分割。
サイズが近いものをまとめてブロック化。
- 新規オブジェクトはブロック内へ割り当て。
- オブジェクト破棄時にブロックから除去される。
- メモリ断片化が発生し得る。
- 完全に空のブロックは OS へ返却可能。
- 完全空ブロックは decommitted される
- VM では予約状態だが物理メモリにはマップされない
- 既存ブロックに新規割り当てが収まらない場合、新規メモリリージョンを予約。
- 既定ブロックより大きい割り当てはカスタムブロック生成。
- リージョン内の未割り当て領域は物理メモリへマップされない。
- 既存ブロックは近いサイズのオブジェクト割り当てに再利用できる。
- メモリリージョン全体が解放されると、仮想メモリでは確保状態でも物理メモリ側は decommit 返却される場合がある。
- 新規割り当てが既存ブロックに入らない場合 -> カスタムブロック生成/割り当て。
Unity Memory Profiler 分析
Summaries タブ
Memory Usage On Devices: 実機メモリ使用量Allocated Memory Distribution: VM 割り当て分布Managed Heap Utilization: ヒープ利用率Top Unity Objects: メモリを多く使う Unity オブジェクト
- Memory Usage On Devices
Total Resident: 常駐メモリTotal Allocated: 割り当て量
- Allocated Memory Distribution
- カテゴリ別 VM 割り当て分布。
- Native: Native C++
- Graphics: Metal 経由 GPU 割り当て
- Managed: C#
- Executable & Mapped: Clean メモリ、バイナリ、DLL
Untracked: シンボル不明/カテゴリ曖昧な割り当て -> カテゴリ機能は完全ではない (例: Audio Clip)
- カテゴリは完全ではない
種類が曖昧な場合
分類不能な場合
Unity バージョンで分類方針が異なる場合 Unknown/Others/Untrackedは Unity が分類できない割り当てを意味する。例: プラグイン割り当て (Android ログイン機能実装時など)、Unity アプリ由来の一部割り当て。
- 常駐メモリ比率も表示される。
Untrackedが大きい = 必ず問題、ではない。- 例:
MALLOC_NANO事前確保されたヒープ領域を示す。
Allocated size: 502.1 MB
Resident memory: 3.3 MB
- Managed Heap Utilization
- ヒープ利用率を示す。
直接制御しづらい領域。
- Virtual Machine
- Empty Heap Space
- Objects
- Virtual Machine
- スクリプティング本体のための VM 割り当て。
- ジェネリクス、型メタデータ、リフレクション。
- ランタイム中に増え続ける傾向。
- 減らす方法
- コードストリップ
```
- コードストリップ
- エンジンコードストリップ: ビルドで未使用の Unity エンジンモジュールコードを除去
- Managed Code Strip Level 両方を使う。リフレクション使用時はエラー化し得るため、link.xml で必要クラスを明示保持する。 ```
- 可能な範囲でリフレクションを使わない
- ジェネリックシェアリング (Unity 2022+)
- Empty Heap Space
- GC 実行構造
- 空きブロック形成にはアルゴリズムがある。
- 新規割り当て発生
- まず連続ヒープ空間 (
Active Empty Heap Space) から割り当て試行 (高速) - 次に空きブロック一覧と残余ヒープ空間をスキャンして割り当て試行 (低速)
- それでも空きがなければ GC Collection トリガー
- Incremental GC 使用時は GC Collection と同時にヒープ拡張できる。
symbol
gc_call_with_allocとして表示される。 - Incremental GC 未使用時は GC 後でも空き不足ならヒープ拡張。
gc_expand_hp_inner - Incremental GC 設定を推奨。
Unity Objects タブ
- Unity オブジェクトごとの割り当て量を表示。
Native Size(C++)、Managed Size(C#)、Graphics Sizeの 3 軸で表示。- 常駐メモリ比率も表示。
All of Memory タブ
- VM 全体の割り当てオブジェクトを表示。
Untracked,Reservedを含む。
Memory Map
- 隠れ機能。
- ページ単位メモリマップ。
- どの主体が該当ページを占有しているか確認できる。
Device + IOACCELERATOR: GPU 向け割り当て
Native Allocation: Unity ネイティブコード由来割り当て - 具体的オブジェクト名は見えないが、どのフレームワーク/バイナリが占有しているか把握可能。
- 512x512 テクスチャの割り当てが 2.7MB?
- Native 1.3MB + Graphics 1.3MB?
- いつ/なぜこの割り当てが起きたかを調べるには iOS ネイティブプロファイラが有効。
- Memory Profiler はスナップショットなので Call Stack 追跡は難しい。
ネイティブプロファイラを使う: Xcode Instruments
- Xcode Instruments 使用方法
- または Xcode から直接 Instruments 起動。
- Xcode Build Settings で Debug Symbol を必ず含める。
- Xcode -> Open Developer Tools を選択
- Instruments テンプレートで
Allocationsを選択
VM Tracker
- アプリの VM 割り当て全体を確認できる。
- Binaries/Code (Clean):
_LINKEDIT,_TEXT,_DATA,_DATA_CONST - “GPU”: Unity GPU 処理領域 (
IOKit,IOSurface,IOAccelerate) - App Allocations: Unity CPU 側処理 (
Malloc_*,VM_ALLOCATE) - Performance tool data
IOSurfaceGraphics 割り当ての常駐率は 100% -> 物理メモリへ 100% 割り当て。- この物理メモリ上限を超えるとアプリは落ちる。
0x10da50000オブジェクトはどこにあるか?
- IL2CPP VM のジェネリックメタデータテーブル初期化過程での割り当てだと確認できる。
Call Stack 検索
- 下部検索欄で検索可能。
- dirty size: 仮想メモリ内で dirty な領域 (resident から外しづらい領域)
- swapped: スワップ済み領域
- resident: 常駐メモリ
Allocationsでも常駐メモリ比率を確認できる。
- 追加情報
Memory Graph: ネイティブメモリスナップショットツールWWDC 2022 -
Profile and optimize your game's memoryアプリ終了原因の切り分け: メモリ問題か?
アプリが落ちない間に Memory Profiler スナップショットを取り、過剰メモリ使用か確認。
iOS Xcode debugger 接続状態でプレイ -> クラッシュ -> 原因を捕まえやすい。
クラッシュがメモリ原因か、別エラーかを判定。
メモリ問題なら:
Memory Profiler の
Unity Objects/SummariesでTotal Committedの大きい領域から順に整理・分析。- レイアウト処理について
- 循環レイアウト問題のため、
- dirty 判定を見てフレーム終端でレイアウト更新。
毎フレーム末尾へ押し出され続けるなら、レイアウト強制更新 API があるか検討。
- その他メモ
- Mobile -> Unified memory
- Windows -> VRAM
- Texture read/write 設定 -> CPU メモリ側にもコピーが載る

















































