언리얼 엔진의 가비지 컬렉션(GC) 시스템은 메모리 관리를 자동화하여 개발자의 부담을 줄이고 메모리 누수를 방지합니다.
이 절에서는 GC의 원리, 작동 방식, 그리고 C++ 프로그래밍에서의 고려사항을 살펴보겠습니다.
가비지 컬렉션의 기본 개념
가비지 컬렉션은 더 이상 사용되지 않는 메모리를 자동으로 식별하고 해제하는 메모리 관리 기법입니다.
주요 특징은 다음과 같습니다.
- 자동 메모리 관리
- 순환 참조 문제 해결
- 댕글링 포인터 방지
언리얼 엔진의 가비지 컬렉션 구현
언리얼 엔진은 'Mark and Sweep' 알고리즘을 기반으로 한 가비지 컬렉션을 구현합니다.
- Mark 단계: 루트 세트에서 시작하여 도달 가능한 모든 객체를 표시
- Sweep 단계: 표시되지 않은 객체를 메모리에서 제거
가비지 컬렉션 대상 객체
언리얼 엔진에서 가비지 컬렉션 대상이 되는 객체는 다음과 같습니다.
- UObject를 상속받은 클래스의 인스턴스
- UPROPERTY 매크로로 선언된 포인터가 참조하는 객체
예시
UPROPERTY 매크로 기반 GC 대상 지정
UPROPERTY 매크로는 객체를 GC 시스템에 등록합니다.
수동 가비지 컬렉션 제어
개발자가 직접 GC를 제어할 수 있는 방법들이 있습니다.
1. AddToRoot() 및 RemoveFromRoot()
- 객체를 루트 세트에 추가하거나 제거하여 GC를 제어할 수 있습니다.
2. MarkPendingKill()
- 객체를 다음 GC 사이클에 삭제되도록 표시합니다.
3. ConditionalBeginDestroy()
가비지 컬렉션이 성능에 미치는 영향
GC는 자동화된 메모리 관리의 편리함을 제공하지만 성능 오버헤드를 동반할 수 있습니다.
- GC 사이클 동안 게임 스레드 일시 정지
- 메모리 단편화
- 예측 불가능한 타이밍의 성능 저하
가비지 컬렉션을 고려한 최적화 전략
- 객체 풀링 사용
- 가능한 경우 UObject 대신 일반 C++ 클래스 사용
- 필요한 경우에만 UPROPERTY 사용
- 대량의 객체 생성 및 소멸 최소화
일반적인 문제 상황과 해결 방법
1. 순환 참조
문제
해결
- 약한 참조 사용 (TWeakObjectPtr)
- 순환 구조 재설계
2. 의도치 않은 객체 소멸
문제 :
AddToRoot()를 사용했지만 RemoveFromRoot()를 호출하지 않음.
해결 :
RAII 패턴 사용
3. 성능 저하
문제 :
빈번한 GC 호출로 인한 성능 저하
해결
- GC 빈도 조절 (엔진 설정 조정)
- 객체 풀링 사용
- 크리티컬 섹션에서 GC 비활성화
Best Practices
1. 명확한 소유권 정의
2. 수명주기 관리 주의
3. 디버깅 도구 활용
- 언리얼 엔진의 메모리 프로파일러 사용
- GC 로깅 활성화
4. 대량 객체 처리 최적화
5. 가비지 컬렉션 친화적인 설계
- 객체 간 강한 결합 최소화
- 컴포넌트 기반 설계 활용
- 리소스의 동적 로딩/언로딩 고려
언리얼 엔진의 가비지 컬렉션 시스템은 메모리 관리를 크게 단순화하지만 효과적으로 활용하기 위해서는 그 작동 원리와 영향을 이해해야 합니다.
GC를 고려한 설계와 최적화를 통해 메모리 누수를 방지하고 성능을 향상시킬 수 있습니다.
필요에 따라 수동 메모리 관리 기법을 병행하는 것이 중요합니다. 객체 풀링, 적절한 UPROPERTY 사용, 그리고 순환 참조 방지 등의 기법을 통해 GC의 부담을 줄일 수 있습니다.