[UE5] 이득우 Part 1. 12강 언리얼 엔진의 메모리 관리

공부 스파이럴·2023년 12월 23일
0

C++ 언어 메모리 관리 문제점

  • 메모리 주소에 직접 접근하는 포인터를 사용해 관리
  • 직접 new, delete를 짝 맞춰줘야 함
  • 잘못된 포인터 예시
    • 메모리 누수(Leak): new를 했는데 delete를 하지 않아 힙에 메모리가 남아있음
    • Dangling 포인터: 이미 할당 해제한 오브젝트의 주소를 가리키는 포인터
    • Wild 포인터: 값이 초기화되지 않아 이상한 주소를 가리키는 포인터
  • 잘못된 포인터 값은 다양한 문제가 생기며 프로그램을 종료시킬 수 있음
  • 규모가 커지고 복잡해질수록 실수할 확률이 증가

  • C++ 이후 언어(JAVA/C# 등)는 문제 해결을 위해 포인터 대신 가비지 컬렉션 시스템을 도입

가비지 컬렉션 시스템

  • 프로그램에서 더 이상 사용하지 않는 오브젝트를 자동으로 감지해 메모리를 회수하는 시스템
  • 동적으로 생성된 모든 오브젝트 정보를 모아둔 저장소를 사용해 사용되지 않는 메모리를 추적
  • 마크-스윕(Mark-Sweep) 방식의 가비지 컬렉션
    1. 저장소에서 최초 검색을 시작하는 루트 오브젝트를 표기
    2. 루트 오브젝트가 참조하는 객체를 찾아 Mark
    3. 마크된 객체로부터 다시 참조하는 객체를 찾아 Mark하고 반복
    4. 저장소에 마크된 객체와 마크되지 않은 객체의 두 그룹이 생김
    5. 카비지 컬렉터가 저장소에서 마크되지 않은 객체들을 메모리 회수(Sweep)

언리얼 엔진의 가비지 컬렉션 시스템

  • 마크-스윕 방식의 가비지 컬렉션 시스템을 자체적으로 구축
  • 지정된 주기마다 몰아서 제거 (CGCycle, 기본 60초)
  • 성능 향상을 위해 병렬 처리, 클러스터링과 같은 기능을 탑재

가비지 컬렉션을 위한 객체 저장소

  • 관리되는 모든 UObject의 정보를 저장하는 전역 변수: GUObjectArray
  • GUObjectArray의 각 요소에는 Flag가 설정되어 있음
  • 가비지 컬렉터가 참고하는 주요 플래그
    • Grabage 플래그: 다른 언리얼 오브젝트로부터의 참조가 없어 회수 예정인 오브젝트
    • RootSet 플래그: 다른 언리얼 오브젝트로부터 참조가 없어도 회수하지 않는 특별한 오브젝트

가비지 컬렉터의 메모리 회수

  • 가비지 컬렉터는 지정된 시간에 따라 주기적으로 회수
    • 기본 60초
  • Garbage 플래그로 설정된 오브젝트를 파악하고 메모리를 안전하게 회수
  • Garbage 플래그는 시스템이 알아서 설정
  • 한번 생성된 오브젝트를 삭제하기 위해서는 C++의 딜리트 키워드를 사용해서 바로 삭제하는 것이 아니라 레퍼런스 정보를 없애 언리얼의 가비지 컬렉터가 자동으로 메모리를 회수하도록 설정

RootSet 플래그의 설정

  • 중요해서 시스템이 실행되는 동안 살아있어야 함
  • AddToRoot 함수를 호출해 RootSet 플래그를 설정
  • 메모리 회수로부터 보호
  • RemoveFromRoot 함수를 호출해 RootSet 플래그를 제거

  • 콘텐츠 관련 오브젝트에는 권장되지 않음

UObject를 통한 포인터 문제 해결

  • 메모리 누수 문제
    • 언리얼 오브젝트는 가비지 컬렉터를 통해 자동으로 해결
    • C++ 오브젝트는 직접 신경써야 함 (스마트 포인터 활용)
  • Dangling 포인터 문제
    • UObject는 이를 탐지하기 위한 함수 제공 ::IsValid()
    • C++ 오브젝트는 직접 신경써야 함 (스마트 포인터 활용)
  • Wild 포인터 문제
    • UObject는 UPROPERTY 속성을 지정하면 자동으로 nullptr 초기화
    • C++ 오브젝트의 포인터는 직접 nullptr로 초기화(또는 스마트 포인터)

회수되지 않는 언리얼 오브젝트

  • 언리얼 엔진 방식으로 참조를 설정한 UObject
    • UPROPERTY로 참조된 UObject
      • 대부분의 경우 이를 사용
    • AddReferencedObject 함수를 통해 참조를 설정
  • RootSet으로 지정된 UObject

  • 오브젝트 선언 기본 원칙
    • 오브젝트 포인터는 가급적 UPROPERTY로 선언
    • 메모리는 가비지컬렉터가 자동으로 관리하도록 위임

일반 클래스에서 언리얼 오브젝트를 관리하는 경우

  • UPROPERT를 사용하지 못하는 일반 C++ 클래스가 언리얼 오브젝트를 관리해야 하는 경우
  • FGCObject 클래스를 상속받은 후 AddReferencedObjects 함수를 구현
  • 함수 구현부에서 관리할 UObject를 추가

  • 컨텐츠 제작에서 자주 사용되진 않음

언리얼 오브젝트의 관리 원칙

  • 생성된 UObject를 유지하기 위해 레퍼런스 참조 방법을 설계할 것
    • 언리얼 오브젝트 내의 언리얼 오브젝트: UPROPERTY 사용
    • 일반 C++ 오브젝트 내의 언리얼 오브젝트: FGCObject의 상속 후 구현
  • 생성된 UObejct를 강제로 지우려 하지 말 것
    • 참조를 끊는다는 생각으로 설계
    • 가비지 컬렉터에게 회수를 재촉할 수는 있음
      • ForceGarbageCollection 함수
    • 콘텐츠 제작에서 Destroy함수를 사용할 수 있으나 내부 동작은 동일
      • 가비지 컬렉터에 위임

실습

  • CGCycle 3초 단축 설정
  • GameInstance에서 Init, Shutdown 오버라이드
  • Init에서 오브젝트 생성 -> 3초 대기 -> 플레이 중지 -> Shutdown에서 오브젝트 유효성 확인

0개의 댓글