[Unite On-Air 2025] 유니티 메모리 이해하기: NET GC와 Unity GC의 차이점
- 성능 최적화의 중요성: 메모리 접근 방식을 현명하게 선택하면 게임 성능을 최대 50배까지 향상시킬 수 있습니다. 🚀
- 데이터 지향 설계 (DOD): 코드를 하드웨어 수준에 가깝게 설계하여 CPU 캐시 및 메모리 작동 방식을 활용하는 것이 핵심입니다. 🧠
- 메모리 계층 구조 이해: CPU 레지스터, 캐시(L1, L2, L3), 메인 메모리 간의 속도 차이를 이해하고, 캐시 미스를 최소화하는 것이 중요합니다. ⚡
- 캐시 라인과 데이터 배치: CPU는 데이터를 64/128바이트 캐시 라인 단위로 가져오므로, 관련 데이터를 메모리에 연속적으로 배치하여 캐시 히트율을 높여야 합니다. 📦
- 참조 타입의 성능 저하: C#의 클래스, 배열, 문자열과 같은 참조 타입은 데이터가 메모리 곳곳에 흩어져 있어 캐시 미스를 유발하고 성능을 저하시킬 수 있습니다. 📉
- 프리패칭 활용: CPU는 다음에 필요할 데이터를 예측하여 미리 캐시에 로드하므로, 단순하고 예측 가능한 데이터 접근 패턴을 유지하는 것이 좋습니다. 🔮
- 가비지 컬렉션 (GC)의 이해:
- Mono GC (Mark & Sweep): 모든 접근 가능한 객체를 마킹하고 나머지를 해제하며, "Stop the World" (STW) 일시 정지로 인해 GC 스파이크가 발생합니다. 🛑
- Unity 점진적 GC: 마킹 단계를 분할하여 STW 시간을 줄이고 GC 스파이크를 완화합니다. 📈
- CoreCLR 세대별 GC (예정): 객체를 수명에 따라 세대별로 분류하고, 짧은 수명 객체에 더 자주 GC를 실행하며, 메모리 압축을 통해 단편화를 줄입니다. ♻️
- Unity 네이티브 GC: C# GC와 별개로 유니티 네이티브 객체를 정리하며,
Resources.UnloadUnusedAssets 등 특정 시점에 실행됩니다. 🌳
unsafe 모드와 고정 크기 버퍼: C#의 unsafe 모드를 활용하여 스트럭트 내에 고정 크기 버퍼를 직접 선언하면, 모든 데이터를 하나의 연속된 메모리 블록에 배치하여 캐시 효율을 극대화할 수 있습니다. 🛠️
ref return 및 포인터 활용: ref return과 포인터 캐스팅을 통해 고정 버퍼 내의 데이터에 안전하고 효율적으로 접근하여 복사 비용을 줄일 수 있습니다. 🔗
- 클래스 대신 스트럭트 사용: 데이터의 연속성을 확보하고 GC 부하를 줄이기 위해 가능한 한 클래스 대신 스트럭트를 사용하는 것이 유리합니다. 🧱
- Unity ECS (Entity Component System): 데이터 지향 설계의 대표적인 예시로, 컴포넌트를 큰 버퍼에 스트럭트 형태로 패킹하여 캐시 친화적인 코드 작성을 유도합니다. 🎮
- 스트럭트 분리 (Data-Oriented Design): 매우 큰 스트럭트를 여러 개의 작은 스트럭트로 분리하여 필요한 데이터만 캐시 라인에 로드함으로써 메모리 효율성을 높일 수 있습니다. ✂️
데브허브 | DEVHUB | [Unite On-Air 2025] 유니티 메모리 이해하기: NET GC와 Unity GC의 차이점