Unity Roadshow 2026 で学んだ内容のまとめ
Unity Roadshow に参加して学んだ内容の簡単な記録
メモリの前提知識
基本内容
-
私たちは仮想メモリ (Virtual Memory、以下 VM) を使用しており、物理メモリを直接コントロールはしない
-
VM はページで構成されており、OS ごとに異なり得る
-
Window / Linux 4kb
-
iOS, macOS, Android 16kb
-
-
ページに対応する物理メモリのフレームが存在する
-
メモリ圧迫の度合いに応じて、仮想メモリを物理メモリにマージする
Page Fault
物理メモリにロードされていないページが必要になった場合を Page Fault と呼ぶ
-
Clean Page: 元データが変わっていないページ。OS がいつでも破棄でき、回収が容易
-
Dirty Page: アプリケーションが新たに書き込みや変更を加えた情報を持つページ。メモリからすぐに退避できない
Memory Footprint
実質的なメモリ圧迫を表す指標で、全 Dirty Page サイズの合計
Memory Footprint != 物理メモリ使用量、常駐メモリ使用量
メモリ管理方法 (Windows vs Linux)
-
予約: 物理メモリを使わず仮想メモリのみを確保
-
コミット: 仮想メモリと同量の物理フレームを割り当てる
-
デコミット: 予約領域は保ったまま物理フレームを解放する
Windows: 明示的な API を提供 (virtual alloc, virtual free)
UNIX Like: 予約とコミットの明示的な分離がない
mmap 呼び出しは暗黙的にメモリを予約する
ページに直接アクセスした時点で遅延コミットが発生
明示的な API はなく、デコミットはヒントとして動作する
OS がいつページを回収するかを決定する
Unity GC における処理
Managed Heap
-
C# のメモリ確保はすべてここで行われる
-
Garbage Collector が管理する
-
Unity Profiler では GC.Alloc として表示される
Garbage Collector
-
有効な参照が存在しない Object を Heap から定期的に回収する
-
GC を実行しても仮想メモリの総使用量は減らない
-
回収済みのオブジェクト領域も OS から見れば Dirty
Segment
連続したページの集合を Segment と定義する
-
Managed Heap は Raw Page ではなく Segment を使う
-
1 つの Segment に複数の Object が割り当てられ得る
-
割り当てて解放した後、内部的には未使用でも RAM 使用として表示される (Dirty のため)
-
大半は何の問題もなく、すぐに再利用される
-
長時間空のままだと常駐メモリの無駄になるため、Unity GC が断続的に処理する
Unity GC の役割
-
リマッピング戦略を採用している
-
GC が複数回実行された後、Old Empty Segment を Drop し、同サイズの New Segment を割り当てる
-
Windows Remap 戦略: Segment を解放せず Decommit (明示的に扱えるため)
-
UNIX Like Remap 戦略: munmap & mmap による Remap (Hot Page 効果でキャッシュヒット率が高い)
-
これにより常駐メモリを減らせるという利点がある
GC.Collect を何度も呼ぶべきでない理由
-
Segment のリリースを強制的に実行しないこと
-
Collect はコストが高く、アプリがフリーズする恐れがある
-
もともとの意図は長時間空き状態のセグメントをデコミットすることにある
-
一時的に空になっただけのセグメントをデコミットするのは意図と異なる
-
強制的にデコミットしてもパフォーマンスは改善せず、CPU コストだけが発生する
- どうせ次フレームで再利用されるセグメントまでデコミットする状況になる
Managed Heap の VM 使用量は増えるばかりで絶対に減らない
-
Heap は大きくなるだけで縮小しない
-
Boehm GC 本来の特性
-
仮想メモリの割り当て量を Trim する理由がない
-
大きな仮想メモリ != 大きな物理メモリ使用量
-
セグメントはどうせ再利用される
-
ゲームオブジェクトは毎フレーム頻繁に生成・破棄される
-
オブジェクトが破棄されるたびにセグメントを縮めるのは CPU 性能の浪費にしかならない
-
Unity Boehm GC
-
Unity の Incremental Boehm GC は保守的な GC (Conservative GC)
-
これは Unity が、過去に書かれた C# コードを C++ に変換する処理をサポートする IL2CPP を提供したことで主に使われるようになった
- IL2CPP によって多様な環境への対応が可能になったが、C# と違いメモリ GC は自動化されないため、オープンソースの GC である Boehm GC を採用したと考えられる
-
性能よりも安定性を重視し、ポインタ「のように」見える値はすべて参照とみなす
-
GC 中にオブジェクトの位置を変えない -> コンパクションがなく、メモリ断片化に弱い
Compaction について
メモリ断片化の解消に有利なメモリ圧縮処理。未使用領域をすべて空けて再配置する
正確な参照情報が必要なため、保守的な GC では不可能
オブジェクトの位置を移動するにはコストがかかる (Stop The World + 実際の移動)
CoreCLR GC
CoreCLR GC は Precise GC で、メモリ・スタック・レジスタ上のどの値が参照かを正確に把握する方式の GC
そして .NET は CoreCLR をコアエンジンとして採用している (CLR: Common Language Runtime)
CoreCLR がサポートする点
CoreCLR GC
JIT コンパイラ
Excecption Control
misc...
この CoreCLR GC (以下 .NET GC) は世代別メモリ管理方式を採用している
-
Heap を 3 世代に分けて管理する
-
Gen 0: 生成直後の新しいオブジェクト中心。頻繁かつ高速な GC が走る (大半のオブジェクトはここで消える)
-
Gen 1: Gen 0 を生き延びたオブジェクトが移される Buffer の役割
-
Gen 2: 長く生き残っている重いオブジェクトが置かれる領域。ここの GC はコストが比較的大きい
-
LOH: 非常に大きいオブジェクトはこの領域に配置される
プロモーション過程でメモリ Compaction を行うことで断片化を抑えられる
LOH の場合 STW が長くなるため、以前は Compaction しなかったが、現在は明示的にオプション化して扱っているとのこと
移行の価値?
基本的に Unity が .NET に移行することで生じる副次的な影響として GC が変わるのであって、GC がメインではない、という点が話されていた。それでも価値を挙げるなら...
-
Compaction が利用できるためメモリ断片化が減る
-
GC コストを予測可能にできる
댓글 작성
게시글에 대한 의견을 남겨 주세요.