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
-
Unity의 Incremental Boehm GC는 보수적인 GC (Conservative GC)
-
이는 유니티가 과거 작성한 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 (이하 닷넷 GC) 는 세대 기반 메모리 관리 방식을 사용함
-
Heap을 3개 세대로 나누어 관리
-
0세대: 방금 생성된 새로운 객체 위주, 빈번하고 빠른 GC가 발생 (대부분 여기서 객체가 죽게 됨)
-
1세대: 0세대에서 살아남은 객체들이 이동하는 Buffer 역할
-
2세대: 오랫동안 살아남은 무거운 객체가 존재하는 곳, GC가 이곳을 정리하는 것은 비용 소모가 큰 편
-
LOH: 아주 큰 객체는 이 공간으로 배치
프로모션 과정에서 메모리 Compaction을 처리하게 되면서 단편화를 줄일 수 있음
LOH의 경우 STW가 길어지기 때문에 오래 전에는 Compaction 하지 않았지만 지금은 명시적으로 옵션화해서 처리하고 있다고 함
전환의 가치?
기본적으로 Unity가 .NET으로 넘어가면서 발생하는 부수 효과로 GC가 바뀌는 것이며, GC가 메인은 아니라는 점을 이야기했음. 그러나 가치를 찾아보자면...
-
Compation이 지원 가능하므로, 메모리 파편화가 줄어듬
-
GC 비용을 예측 가능하게 만들 수 있음
댓글 작성
게시글에 대한 의견을 남겨 주세요.