Unity Roadshow 2026 배운 내용 정리
Unity Roadshow 행사 가서 배운 내용들 간단 기록
메모리 사전 지식
기본 내용
-
우리는 가상 메모리 (Virtual Memory, 이하 VM) 를 사용하며, 물리 메모리를 직접적으로 컨트롤하지 않는다
-
VM은 페이지로 구성되어 있으며, OS마다 상이할 수 있음
-
Window / Linux 4kb
-
IOS, MacOS, Android 16kb
-
-
페이지에 대응하는 물리 메모리의 프레임 존재
-
메모리 압박 수준에 따라서 가상 메모리를 물리 메모리에 병합한다
Page Fault
물리 메모리에 로드되지 않은 페이지가 필요한 경우를 Page Fault라고 함
-
Clean Page: 원본 데이터가 변하지 않은 Page, OS에서 언제든 해지할 수 있고 회수가 쉬움
-
Dirty Page: 응용 프로그램이 새로 작성하거나 수정한 정보를 가진 페이지, 메모리에서 바로 내릴 수 없다
Memory Footprint
실질적인 메모리 압박을 표현하는 지표이며, 전체 더티 페이지 크기를 합산한 결과
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 사용
-
한 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 여러 번 실행하면 안되는 이유
- 세그먼트 릴리즈를 강제로 실행하지 말 것
- Collect 비싸고, 앱 프리징될 수 있음
- 의도 자체가 오랫동안 비어있는 세그먼트의 디커밋
- 잠깐 빈 세그먼트 디커밋은 의도와 다름
- 강제로 디커밋 시 성능 개선은 없고 CPU 비용만 발생
- 어차피 다음 프레임에 재사용되는 세그먼트도 디커밋되는 상황이 유발
Managed Heap의 VM 사용량은 커지기만 하고 절대 줄지 않음
- Heap은 커지기만 하고 줄어들지 않음
- Boehm GC의 원래 특징
- 가상 메모리 할당량을 Trim할 이유가 없음
- 큰 가상 메모리 != 큰 물리 메모리 사용량
- 세그먼트 어차피 재사용
- 게임 오브젝트들 매 프레임 자주 생성 및 파괴됨
- 오브젝트가 파괴될 때마다 세그먼트를 줄이는 것은 CPU 성능만 낭비됨
이하 아직 정리하지 않은 내용
Unity Boehm GC vs CoreCLR GC
- 유니티의 Incremental Boehm GC는 보수적 GC (feat IL2CPP)
- 안정성 > 성능
- 포인터 "처럼" 보이는 모든 값들은 전부 참조로 간주
- GC 도중 오브젝트들을 위치를 변경하지 않음
- 컴팩팅을 지원하지 않음
- 메모리 파편화 약점 보유
- 안정성 > 성능
컴팩팅
메모리 파편화 해소에 유리
정확한 참조 정보가 필요하며 보수적 GC에서는 불가
오브젝트 위치를 옯기는 데에 비용 발생
- CoreCLR에 포함되는 GC
- 메모리 파편화 감소/ 예측 가능한 GC 비용
- CoreCLR의 핵심 가치는 유니티 닷넷의 현대화
- GC 차이는 부수 효과임에 유의할 것
- Precise GC 모델
- 레지서터, 스택의 값이 관리되는 참조인지 정확한 판단 가능
- 컴팩션 지원 (단편화 일부 해소)
- 세대별 GC
- 수명에 따라 주기 관리
대부분의 오브젝트들은 초기에 죽는다
매번 전체 힙 탐색 대신 작은 젊은 세대부터 우선 회수 -> GC 비용 최적화
새 오브젝트는 Gen0에 할당
오래 살아남은 오브젝트들을 주기적으로 승격
Boehm GC
Native RP
CoreCLR, Precise GC 원리
기본적으로 Java, C# 같은 언어는 명시적으로 메모리를 관리하지 않지만 프레임워크에는 메모리 관리자가 필요하다.
세대별 GC 이거 스프링에서 쓰는 그것과 차이가 뭔지?
댓글 작성
게시글에 대한 의견을 남겨 주세요.