PART 5 · 강의 4/4

멀티스레드 GC

Parallel GC, Worker Thread 활용, UE 5.3+ 개선사항으로 GC를 병렬화하는 최신 기법

SECTION 01

GC의 스레딩 모델

UE5 GC가 멀티코어를 활용하는 방식

GC 단계별 스레딩

단계스레딩설명
GC Lock 획득Game Thread다른 스레드의 UObject 조작 차단
Unreachable 초기화ParallelWorker Thread로 병렬 플래그 설정
Root Set 수집Game ThreadFGCObject 목록 순회
참조 순회 (Mark)ParallelTFastReferenceCollector 병렬 실행
Unhash UnreachableGame Thread해시 테이블 정리
BeginDestroyGame Thread소멸 시작 (순차)
FinishDestroyAsync 가능비동기 완료 대기
GC Lock의 영향

GC가 진행 중이면 FGCScopeGuard를 통해 다른 스레드의 UObject 생성/파괴가 차단됩니다. 이는 게임 스레드를 포함한 모든 스레드에 영향을 미칩니다. 따라서 GC 시간을 최소화하는 것이 전체 프레임 성능에 중요합니다.

SECTION 02

Parallel Mark 구현

참조 순회를 병렬로 처리하는 메커니즘

병렬 참조 수집기

엔진 소스 (간략화) // 병렬 Mark 단계 void PerformReachabilityAnalysisOnObjects(FGCArrayStruct& ArrayStruct) { // 작업을 Worker Thread에 분배 int32 NumWorkers = FTaskGraphInterface::Get() .GetNumWorkerThreads(); // 객체 목록을 Worker 수에 맞게 분할 int32 ObjectsPerWorker = ArrayStruct.Num() / NumWorkers; ParallelFor(NumWorkers, [&](int32 WorkerIndex) { int32 Start = WorkerIndex * ObjectsPerWorker; int32 End = (WorkerIndex == NumWorkers - 1) ? ArrayStruct.Num() : Start + ObjectsPerWorker; // 각 Worker가 할당된 범위의 객체 참조를 수집 TFastReferenceCollector<FGCReferenceProcessor> Collector; Collector.ProcessObjectArray(ArrayStruct, Start, End); }); // 결과 병합 (Atomic 연산 사용) }
병렬 GC의 스레드 안전성

병렬 Mark에서 여러 Worker가 같은 객체를 Reachable로 마킹할 수 있습니다. 이를 위해 EInternalObjectFlags의 설정/해제는 atomic 연산을 사용합니다. 또한 Worker 간 작업 큐는 lock-free 구조를 사용하여 경합을 최소화합니다.

SECTION 03

UE 5.3+ GC 개선사항

최신 엔진 버전의 GC 성능 개선

버전별 GC 개선

버전개선사항효과
UE 5.0TObjectPtr 도입, 쓰기 배리어Incremental GC 안전성 향상
UE 5.1Cluster GC 개선대규모 에셋의 GC 효율 향상
UE 5.2Incremental Mark 최적화Mark 단계 분산 효율 개선
UE 5.3Parallel Reachability Analysis멀티코어 활용도 대폭 향상
UE 5.4Incremental Unhash 도입Unhash 단계도 점진적 처리
UE 5.5+GC 스케줄러 개선적응형 GC 타이밍
C++ // UE 5.3+ 병렬 Reachability 활용 // DefaultEngine.ini gc.AllowParallelReachability=True // 5.3+에서 기본 활성화 gc.ParallelGCBatchSize=4096 // Worker당 처리 배치 크기 // 5.5+ 적응형 GC 스케줄링 // 프레임 여유 시간에 맞춰 자동으로 GC 작업량 조절 gc.AdaptiveGCBudget=True
SECTION 04

멀티스레드 GC 주의사항

병렬 GC 환경에서의 안전한 프로그래밍

스레드 안전 가이드

C++ // 1. 다른 스레드에서 UObject 접근 시 // GC Lock을 획득해야 함 { FGCScopeGuard GCGuard; // 이 스코프 내에서는 GC가 실행되지 않음 UObject* Obj = SomeWeakPtr.Get(); if (Obj) { // 안전하게 사용 } } // 2. 비동기 태스크에서 UObject 참조 TWeakObjectPtr<UMyObject> WeakObj = MyObj; AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [WeakObj]() { // 반드시 Game Thread에서 UObject 접근 AsyncTask(ENamedThreads::GameThread, [WeakObj]() { if (UMyObject* Obj = WeakObj.Get()) { Obj->ProcessResult(); } }); });
절대 하지 말아야 할 것
  • 백그라운드 스레드에서 NewObject / SpawnActor 호출
  • GC Lock 없이 다른 스레드에서 UObject 프로퍼티 수정
  • 백그라운드에서 raw UObject* 캐싱 (TWeakObjectPtr 사용)
  • FGCScopeGuard를 장시간 유지 (GC 차단 → 메모리 증가)
SUMMARY

핵심 요약

이 강의에서 배운 내용
  • UE5 GC의 Mark 단계는 Worker Thread에서 병렬로 실행됩니다
  • GC Lock은 GC 진행 중 모든 스레드의 UObject 조작을 차단합니다
  • UE 5.3+에서 Parallel Reachability Analysis로 멀티코어 활용이 크게 향상되었습니다
  • 다른 스레드에서 UObject 접근 시 FGCScopeGuard로 보호해야 합니다
  • 비동기 태스크에서는 TWeakObjectPtr + Game Thread 콜백 패턴을 사용합니다
PRACTICE

도전 과제

배운 내용을 직접 실습해보세요

실습 1: 병렬 GC 설정 확인 및 테스트

gc.AllowParallelGC와 gc.NumReachabilityWorkersToUse의 현재 설정값을 확인하고, 값을 변경하면서 GC Mark 단계 소요 시간의 변화를 측정하세요. CPU 코어 수에 따른 최적 워커 수를 실험적으로 도출하세요.

실습 2: GC Lock과 스레드 안전성 테스트

멀티스레드 환경에서 GC가 실행되는 동안 다른 스레드에서 UObject 접근 시 발생할 수 있는 문제를 재현하세요. FGCScopeGuard, IsInGameThread(), IsGarbageCollecting() 등의 API를 사용한 안전한 코드 패턴을 구현하세요.

심화 과제: 멀티스레드 GC 성능 프로파일링

Unreal Insights의 CPU 타이밍 뷰를 사용하여 병렬 GC의 스레드별 작업 분포를 분석하세요. Reachability Analysis Worker들의 작업량 밸런스를 확인하고, 불균형이 있다면 원인을 분석하여 개선 방안을 제시하세요.