PART 2 ยท ๊ฐ•์˜ 1/4

Mark ๋‹จ๊ณ„ ๋ถ„์„

Root Set ๊ตฌ์„ฑ, Reachability ๋ถ„์„, TFastReferenceCollector์˜ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ์‹ฌ์ธต ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค

SECTION 01

Root Set ๊ตฌ์„ฑ

GC ์ฐธ์กฐ ๊ทธ๋ž˜ํ”„์˜ ์‹œ์ž‘์ ์ธ Root Set์˜ ๊ตฌ์„ฑ ์š”์†Œ

Root Set์ด๋ž€?

Root Set์€ GC์— ์˜ํ•ด ์ ˆ๋Œ€ ์ˆ˜์ง‘๋˜์ง€ ์•Š๋Š” ์ตœ์ƒ์œ„ ๊ฐ์ฒด ์ง‘ํ•ฉ์ž…๋‹ˆ๋‹ค. Mark ๋‹จ๊ณ„์—์„œ GC๋Š” Root Set์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์ฐธ์กฐ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ(Reachable) ๊ฐ์ฒด๋ฅผ ๋งˆํ‚นํ•ฉ๋‹ˆ๋‹ค.

Root Set ๊ตฌ์„ฑ ์š”์†Œ // Root Set์— ํฌํ•จ๋˜๋Š” ๊ฐ์ฒด๋“ค 1. AddToRoot() ํ˜ธ์ถœ๋œ ๊ฐ์ฒด โ””โ”€โ”€ EInternalObjectFlags::RootSet ํ”Œ๋ž˜๊ทธ ์„ค์ • 2. Class Default Objects (CDO) โ””โ”€โ”€ RF_ClassDefaultObject ํ”Œ๋ž˜๊ทธ 3. ๋กœ๋“œ๋œ ํŒจํ‚ค์ง€์˜ Standalone ๊ฐ์ฒด โ””โ”€โ”€ RF_Standalone ํ”Œ๋ž˜๊ทธ 4. FGCObject ํŒŒ์ƒ ์ธ์Šคํ„ด์Šค โ””โ”€โ”€ AddReferencedObjects()๋ฅผ ํ†ตํ•œ ์ฐธ์กฐ ๋“ฑ๋ก 5. ๊ธ€๋กœ๋ฒŒ UObject ์ฐธ์กฐ โ””โ”€โ”€ GEngine, GWorld ๋“ฑ ์—”์ง„ ์‹ฑ๊ธ€ํ„ด 6. UObject::AddReferencedObjects ์˜ค๋ฒ„๋ผ์ด๋“œ โ””โ”€โ”€ ์ปค์Šคํ…€ ์ฐธ์กฐ ๋“ฑ๋ก
C++ // AddToRoot / RemoveFromRoot ์‚ฌ์šฉ UMyObject* ImportantObj = NewObject<UMyObject>(); ImportantObj->AddToRoot(); // Root Set์— ์ถ”๊ฐ€, GC ๋ณดํ˜ธ // ๋” ์ด์ƒ ํ•„์š” ์—†์„ ๋•Œ ImportantObj->RemoveFromRoot(); // Root Set์—์„œ ์ œ๊ฑฐ, GC ๋Œ€์ƒ // Root Set ์—ฌ๋ถ€ ํ™•์ธ bool bIsRoot = ImportantObj->IsRooted();
AddToRoot() ๋‚จ์šฉ ์ฃผ์˜

AddToRoot()๋Š” ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ์˜๊ตฌ์ ์œผ๋กœ GC์—์„œ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค. RemoveFromRoot()๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ UPROPERTY ์ฐธ์กฐ๋งŒ์œผ๋กœ ์ถฉ๋ถ„ํ•˜๋ฉฐ, AddToRoot๋Š” ๊ธ€๋กœ๋ฒŒ ๋งค๋‹ˆ์ € ๊ฐ™์€ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

SECTION 02

Reachability ๋ถ„์„

๊ฐ์ฒด์˜ ๋„๋‹ฌ ๊ฐ€๋Šฅ์„ฑ์„ ํŒ์ •ํ•˜๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜

Mark-and-Sweep ์•Œ๊ณ ๋ฆฌ์ฆ˜

UE5์˜ GC๋Š” Mark-and-Sweep ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Mark ๋‹จ๊ณ„์—์„œ๋Š” Root Set์—์„œ ์‹œ์ž‘ํ•˜์—ฌ ์ฐธ์กฐ ๊ทธ๋ž˜ํ”„๋ฅผ BFS(๋„ˆ๋น„ ์šฐ์„  ํƒ์ƒ‰)๋กœ ์ˆœํšŒํ•˜๋ฉฐ ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ๋งˆํ‚นํ•ฉ๋‹ˆ๋‹ค.

์•Œ๊ณ ๋ฆฌ์ฆ˜ (์˜์‚ฌ ์ฝ”๋“œ) PerformReachabilityAnalysis() { // 1๋‹จ๊ณ„: ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ Unreachable๋กœ ์ดˆ๊ธฐํ™” for (each FUObjectItem in GUObjectArray) { if (!IsInDisregardForGCPool(Item)) Item.SetFlags(EInternalObjectFlags::Unreachable); } // 2๋‹จ๊ณ„: Root Set ๊ฐ์ฒด๋“ค์„ ์ž‘์—… ํ์— ์ถ”๊ฐ€ TArray<UObject*> ObjectsToProcess; for (each RootObject) { RootObject.ClearFlags(Unreachable); // Reachable! ObjectsToProcess.Add(RootObject); } // 3๋‹จ๊ณ„: BFS๋กœ ์ฐธ์กฐ ๊ทธ๋ž˜ํ”„ ์ˆœํšŒ while (ObjectsToProcess.Num() > 0) { UObject* Current = ObjectsToProcess.Pop(); // ํ† ํฐ ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฐธ์กฐ ์ˆ˜์ง‘ for (each UObject* Ref in Current.References) { if (Ref.HasFlag(Unreachable)) { Ref.ClearFlags(Unreachable); // Reachable! ObjectsToProcess.Add(Ref); } } } // ๊ฒฐ๊ณผ: Unreachable ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚จ์€ ๊ฐ์ฒด = ๊ฐ€๋น„์ง€ }
UE5์˜ Reachability ๊ฐœ์„ 

UE5์—์„œ๋Š” TObjectPtr๋ฅผ ํ†ตํ•œ ์ฐธ์กฐ ๋ณ€๊ฒฝ์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ถ”์ ๋ฉ๋‹ˆ๋‹ค. Incremental GC ๋ชจ๋“œ์—์„œ GC ์‚ฌ์ดํด ์ค‘๊ฐ„์— ์ƒˆ๋กœ์šด ์ฐธ์กฐ๊ฐ€ ์ƒ๊ธฐ๋ฉด, TObjectPtr์˜ ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด๊ฐ€ ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ์ฆ‰์‹œ Reachable๋กœ ๋งˆํ‚นํ•˜์—ฌ ์ž˜๋ชป๋œ ์ˆ˜์ง‘์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

SECTION 03

TFastReferenceCollector

๊ณ ์„ฑ๋Šฅ ์ฐธ์กฐ ์ˆ˜์ง‘๊ธฐ์˜ ๋‚ด๋ถ€ ๋™์ž‘

TFastReferenceCollector์˜ ์—ญํ• 

TFastReferenceCollector๋Š” UE5 GC์˜ ํ•ต์‹ฌ ์ฐธ์กฐ ์ˆ˜์ง‘๊ธฐ์ž…๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ƒ์„ฑ๋œ ์ฐธ์กฐ ํ† ํฐ ์ŠคํŠธ๋ฆผ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด ๋‚ด๋ถ€์˜ UObject ์ฐธ์กฐ๋ฅผ ๊ทน๋„๋กœ ๋น ๋ฅด๊ฒŒ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค.

์—”์ง„ ์†Œ์Šค (๊ฐ„๋žตํ™”) template<typename ReferenceProcessorType> class TFastReferenceCollector { // ์ฐธ์กฐ ํ† ํฐ ์ŠคํŠธ๋ฆผ ๊ธฐ๋ฐ˜ ๋น ๋ฅธ ์ˆœํšŒ void ProcessObjectArray( FGCArrayStruct& ArrayStruct, TArray<UObject*>& ObjectsToSerialize) { for (UObject* Obj : ObjectsToSerialize) { // ํ† ํฐ ์ŠคํŠธ๋ฆผ์—์„œ ์ฐธ์กฐ ์˜คํ”„์…‹์„ ์ง์ ‘ ์ฝ์Œ FGCReferenceTokenStream& TokenStream = Obj->GetClass()->GetReferenceTokenStream(); for (auto& Token : TokenStream) { switch (Token.Type) { case GCRT_Object: HandleTokenStreamObjectReference( *(UObject**)((uint8*)Obj + Token.Offset)); break; case GCRT_ArrayObject: HandleArrayReference( *(TArray<UObject*>*)((uint8*)Obj + Token.Offset)); break; case GCRT_AddReferencedObjects: Obj->GetClass()->CallAddReferencedObjects(Obj, Collector); break; } } } } };

ํ† ํฐ ์ŠคํŠธ๋ฆผ vs Serialize ๋ฐฉ์‹ ๋น„๊ต

ํ•ญ๋ชฉ ํ† ํฐ ์ŠคํŠธ๋ฆผ (ํ˜„์žฌ) Serialize (๋ ˆ๊ฑฐ์‹œ)
์†๋„ ๋งค์šฐ ๋น ๋ฆ„ ๋А๋ฆผ (๊ฐ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ)
๋ฉ”๋ชจ๋ฆฌ ํ† ํฐ ์ŠคํŠธ๋ฆผ ์ €์žฅ ์ถ”๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์—†์Œ
๋™์ž‘ ๋ฐฉ์‹ ์˜คํ”„์…‹ ๊ธฐ๋ฐ˜ ์ง์ ‘ ์ ‘๊ทผ Serialize() ๊ฐ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ
๋ณ‘๋ ฌํ™” ์šฉ์ด ์–ด๋ ค์›€
SECTION 04

Mark ๋‹จ๊ณ„์˜ ์ „์ฒด ํ๋ฆ„

CollectGarbage() ํ˜ธ์ถœ๋ถ€ํ„ฐ Mark ์™„๋ฃŒ๊นŒ์ง€์˜ ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ

GC ํŠธ๋ฆฌ๊ฑฐ์—์„œ Mark ์™„๋ฃŒ๊นŒ์ง€

์ „์ฒด ํ๋ฆ„ CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS) โ”‚ โ”œโ”€โ”€ AcquireGCLock() โ”‚ โ””โ”€โ”€ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์˜ UObject ์ƒ์„ฑ/ํŒŒ๊ดด ์ฐจ๋‹จ โ”‚ โ”œโ”€โ”€ PerformReachabilityAnalysis() โ”‚ โ”œโ”€โ”€ Phase 1: ์ดˆ๊ธฐํ™” โ”‚ โ”‚ โ”œโ”€โ”€ ๋ชจ๋“  ๊ฐ์ฒด Unreachable ๋งˆํ‚น โ”‚ โ”‚ โ””โ”€โ”€ DisregardForGC ์˜์—ญ ๊ฑด๋„ˆ๋›ฐ๊ธฐ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Phase 2: Root Set ์ˆ˜์ง‘ โ”‚ โ”‚ โ”œโ”€โ”€ RootSet ํ”Œ๋ž˜๊ทธ ๊ฐ์ฒด ์ˆ˜์ง‘ โ”‚ โ”‚ โ”œโ”€โ”€ FGCObject ์ธ์Šคํ„ด์Šค ์ˆ˜์ง‘ โ”‚ โ”‚ โ””โ”€โ”€ AddReferencedObjects ์ฝœ๋ฐฑ ์‹คํ–‰ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ Phase 3: ์ฐธ์กฐ ๊ทธ๋ž˜ํ”„ ์ˆœํšŒ โ”‚ โ”‚ โ”œโ”€โ”€ TFastReferenceCollector ์‚ฌ์šฉ โ”‚ โ”‚ โ”œโ”€โ”€ ํ† ํฐ ์ŠคํŠธ๋ฆผ ๊ธฐ๋ฐ˜ ์ฐธ์กฐ ์ˆ˜์ง‘ โ”‚ โ”‚ โ””โ”€โ”€ Reachable ๊ฐ์ฒด ๋งˆํ‚น โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ Phase 4: (์„ ํƒ) Incremental ์ค‘๋‹จ์  โ”‚ โ””โ”€โ”€ gc.TimeLimitSeconds ์ดˆ๊ณผ ์‹œ ๋‹ค์Œ ํ”„๋ ˆ์ž„์œผ๋กœ โ”‚ โ””โ”€โ”€ โ†’ Sweep ๋‹จ๊ณ„๋กœ ์ง„ํ–‰ (๋‹ค์Œ ๊ฐ•์˜)
GC Lock์˜ ์ค‘์š”์„ฑ

Mark ๋‹จ๊ณ„์—์„œ๋Š” FGCCSyncObject๋ฅผ ํ†ตํ•ด GC Lock์„ ํš๋“ํ•ฉ๋‹ˆ๋‹ค. ์ด Lock์ด ํ™œ์„ฑํ™”๋œ ๋™์•ˆ์—๋Š” ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ UObject๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ํŒŒ๊ดดํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. IsGarbageCollecting()์œผ๋กœ ํ˜„์žฌ GC๊ฐ€ ์ง„ํ–‰ ์ค‘์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SUMMARY

ํ•ต์‹ฌ ์š”์•ฝ

์ด ๊ฐ•์˜์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ
  • Root Set์€ AddToRoot, CDO, RF_Standalone, FGCObject ๋“ฑ์œผ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ GC์˜ ์‹œ์ž‘์ ์ž…๋‹ˆ๋‹ค
  • Mark ๋‹จ๊ณ„๋Š” ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ Unreachable๋กœ ์ดˆ๊ธฐํ™”ํ•œ ํ›„, Root Set์—์„œ BFS๋กœ Reachable ๊ฐ์ฒด๋ฅผ ๋งˆํ‚นํ•ฉ๋‹ˆ๋‹ค
  • TFastReferenceCollector๋Š” ํ† ํฐ ์ŠคํŠธ๋ฆผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฐธ์กฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๋ฉฐ, ๊ฐ€์ƒ ํ•จ์ˆ˜ ํ˜ธ์ถœ ์—†์ด ๋น ๋ฅด๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค
  • UE5์˜ TObjectPtr๋Š” ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด๋ฅผ ํ†ตํ•ด Incremental GC ์ค‘ ์ƒˆ ์ฐธ์กฐ๋ฅผ ์‹ค์‹œ๊ฐ„ ์ถ”์ ํ•ฉ๋‹ˆ๋‹ค
  • GC Lock์€ Mark ๋‹จ๊ณ„ ๋™์•ˆ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์˜ UObject ์กฐ์ž‘์„ ์ฐจ๋‹จํ•˜์—ฌ ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค
PRACTICE

๋„์ „ ๊ณผ์ œ

๋ฐฐ์šด ๋‚ด์šฉ์„ ์ง์ ‘ ์‹ค์Šตํ•ด๋ณด์„ธ์š”

์‹ค์Šต 1: Root Set ๊ตฌ์„ฑ ์š”์†Œ ํ™•์ธ

์ฝ˜์†” ๋ช…๋ น์–ด obj list -gc๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ํ˜„์žฌ Root Set์— ํฌํ•จ๋œ ๊ฐ์ฒด ๋ชฉ๋ก์„ ํ™•์ธํ•˜์„ธ์š”. AddToRoot()๋กœ ์ปค์Šคํ…€ UObject๋ฅผ Root Set์— ์ถ”๊ฐ€ํ•œ ํ›„ ๋ชฉ๋ก ๋ณ€ํ™”๋ฅผ ๊ด€์ฐฐํ•˜๊ณ , RemoveFromRoot()๋กœ ํ•ด์ œ ํ›„ GC ์ˆ˜์ง‘ ์—ฌ๋ถ€๋ฅผ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

์‹ค์Šต 2: Reachability ๋ถ„์„ ์‹œ๊ฐํ™”

5๊ฐœ์˜ UObject๋ฅผ ์ฒด์ธ ํ˜•ํƒœ๋กœ ์ฐธ์กฐ์‹œํ‚ค๊ณ (A->B->C->D->E), ์ค‘๊ฐ„ ๋งํฌ(C์˜ ์ฐธ์กฐ)๋ฅผ nullptr๋กœ ์„ค์ •ํ•œ ํ›„ ForceGarbageCollection()์„ ํ˜ธ์ถœํ•˜์„ธ์š”. ์–ด๋–ค ๊ฐ์ฒด๊ฐ€ ์ˆ˜์ง‘๋˜๋Š”์ง€ BeginDestroy ์˜ค๋ฒ„๋ผ์ด๋“œ ๋กœ๊ทธ๋กœ ํ™•์ธํ•˜์„ธ์š”.

์‹ฌํ™” ๊ณผ์ œ: TFastReferenceCollector ๋™์ž‘ ํ”„๋กœํŒŒ์ผ๋ง

๋Œ€๋Ÿ‰์˜ UObject(10,000๊ฐœ ์ด์ƒ)๋ฅผ ๋‹ค์–‘ํ•œ ์ฐธ์กฐ ํŒจํ„ด(์„ ํ˜•, ํŠธ๋ฆฌ, ๊ทธ๋ž˜ํ”„)์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ , stat gc ๋ช…๋ น์–ด๋กœ Mark ๋‹จ๊ณ„ ์†Œ์š” ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜์„ธ์š”. ์ฐธ์กฐ ๊ตฌ์กฐ์— ๋”ฐ๋ฅธ Mark ์‹œ๊ฐ„ ์ฐจ์ด๋ฅผ ๋ถ„์„ ๋ณด๊ณ ์„œ๋กœ ์ž‘์„ฑํ•˜์„ธ์š”.