C++에는 가비지 컬렉션 기능이 없기 때문에, 언리얼 엔진에서는 이를 자체적으로 만들어 사용한다.
가비지 컬렉션은 리플렉션 시스템에 의존하는 엔진 내 기능 중 하나이다.
리플렉션 시스템에 대해 자세한 내용은 https://ciel45.tistory.com/127
가비지 컬렉션이란?
- 우리가 C++로 동적 할당을 사용할 때 new, delete를 사용하는데, 실수로 사용하지 않는 객체에 delete를 안하면 메모리 누수가 발생한다.
- 또한 실수로 사용 중인 객체를 delete해버리면 해당 객체를 가리키던 포인터는 null을 가리키게 되고, 이를 dangling 포인터라 한다.
- 그래서 원활한 메모리 관리를 위해 동적 할당한 메모리를 자동으로 해제해주는 것이 가비지 컬렉션이라고 할 수 있다.
언리얼 엔진의 가비지 컬렉션은 Mark and Sweep방식을 사용한다. (유니티와 마찬가지)
Mark and Sweep이란?
- 객체가 필요 없어지더라도 일단 냅뒀다가, 메모리가 부족할 시 알고리즘을 돌려서 한번에 정리하는 방식이다.
- 한 사이클이 Mark와 Sweep 두 단계로 이루어져 있다.
- Mark 단계
- 모든 객체의 마킹을 지워놓는다.
- 어플리케이션의 루트로부터 순회하면서 도달하는 객체들을 마킹한다.
- 어떤 객체가 루트로부터 찾아갈 수 있다면 마킹이 되는 것이다.
- 트리처럼 탐색하면서 마킹을 하는 것과 같다.
- Sweep 단계
- 다 돌았는데 마킹이 되어있지 않은 객체들은 할당을 해제한다.
- Mark 단계
언리얼과 유니티의 가비지 컬렉션의 차이?
- 가비지 컬렉터가 객체 내에서 포인터 변수들의 위치를 알아내는 방식에서 차이가 존재한다.
- 해당 방식에 따라 Mark and Sweep 가비지 컬렉션은 다시 Conservative / Precise로 분류된다.
- Conservative
- 유니티가 사용하는 방식이다.
- 객체를 크기만 주어진 메모리 블록으로 취급한다. 즉 객체의 구체적인 생김새를 모르는 채로 동작한다.
- 이 메모리 블록 내 모든 비트 패턴을 스캔해서, 내가 할당해준 객체로의 주소가 등장하면 그걸 포인터로 간주한다.
- Conservative GC 중 대표적인 것이 유니티가 사용하는 Boehm-Demers-Weiser GC이다.
- Stop the world 방식이다. (GC가 돌아가는 동안에는 모든 스레드 정지)
- 세대, 압축 기능을 지원하지 않는다. (유니티 GC와 C# GC와의 차이점)
- 세대 : 오래 살아남은 객체가 앞으로도 오래 살 것이라는 가정 하에 그룹을 나눠 관리하는 기능
- 압축 : 객체들의 할당 해제 후엔 메모리 곳곳에 빈 공간이 생기는데, 이걸 쭉 밀어서 압축해주는 기능
- 따라서 유니티 프로그래밍에서는 오브젝트 풀과 같은 메모리 단편화를 막기 위한 테크닉이 중요해진다.
- Precise
- 언리얼 엔진이 사용하는 방식이다.
- 객체 내 포인터들의 위치를 정확하게 파악한 채로 동작한다.
- 언리얼 엔진에서는 리플렉션 정보를 활용해 객체 내 포인터들의 위치를 파악한다.
- UProperty 객체가 GC에게 모든 UObject 포인터들의 위치를 알려준다.
- Conservative
또한 언리얼 엔진은 메모리 관리를 위해 스마트 포인터 역시 지원한다.
스마트 포인터
- C++ 에서는 std::shared_ptr, 언리얼에서는 TSharedPtr라는 이름으로 쓰인다.
- 포인터 대신 포인터 객체를 사용한다.
- 별도로 할당된 메모리 블록을 가리키면서, 거기에 대상 객체로의 참조 카운터를 계속 업데이트한다.
- 카운터가 0이 되면 객체에 대해 delete를 호출한다.
- 장점:
- 어떤 객체가 필요 없어졌다는 사실을 즉각적으로 감지할 수 있다.
- 단점:
- 포인터로 객체를 사용하기 때문에 복사/대입/역참조 등의 포인터 연산이 살짝씩 더 무거워진다.
- 순환 참조로 인한 메모리 누수 위험
- 객체들끼리 서로 가리키면 참조 카운트가 영원히 0이 되지 않아 메모리 누수가 발생한다.
- 이를 해결하기 위해 소유권이 있다고 여겨지는 쪽은 TSharedPtr(강한 참조)를 사용하고, 그렇지 않은 쪽은 TWeakPtr(약한 참조)를 사용하게 함으로써 사이클을 끊을 수 있다.
- 약한 참조 횟수는 메모리 해제에 관여하지 않기 때문.
참조:
https://dev.epicgames.com/documentation/ko-kr/unreal-engine/unreal-object-handling-in-unreal-engine
https://www.youtube.com/watch?v=VpEe9DbcZIs&ab_channel=NHNCloud
(좋은 강의 감사합니다)
'언리얼 엔진 5 > 공부' 카테고리의 다른 글
[언리얼 엔진 5] 리플렉션 시스템 (0) | 2024.09.29 |
---|---|
[언리얼 엔진 5] 싱글톤 패턴 (Subsystem) (0) | 2024.04.10 |
[언리얼 엔진 5] 언리얼 엔진에서의 순수 가상함수 사용법 (0) | 2024.04.10 |
[언리얼 엔진 5] Retriggerable Delay 사용법 (FLatentActionInfo) (0) | 2024.04.10 |
[언리얼 엔진 5] 블루프린트에서 디버깅하는 방법 (0) | 2023.12.30 |