Mark ๋จ๊ณ ๋ถ์
Root Set ๊ตฌ์ฑ, Reachability ๋ถ์, TFastReferenceCollector์ ๋์ ์๋ฆฌ๋ฅผ ์ฌ์ธต ๋ถ์ํฉ๋๋ค
Root Set ๊ตฌ์ฑ
GC ์ฐธ์กฐ ๊ทธ๋ํ์ ์์์ ์ธ Root Set์ ๊ตฌ์ฑ ์์
Root Set์ด๋?
Root Set์ GC์ ์ํด ์ ๋ ์์ง๋์ง ์๋ ์ต์์ ๊ฐ์ฒด ์งํฉ์ ๋๋ค. Mark ๋จ๊ณ์์ GC๋ Root Set์์ ์์ํ์ฌ ์ฐธ์กฐ๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉฐ ๋๋ฌ ๊ฐ๋ฅํ(Reachable) ๊ฐ์ฒด๋ฅผ ๋งํนํฉ๋๋ค.
// 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 ์ค๋ฒ๋ผ์ด๋
โโโ ์ปค์คํ
์ฐธ์กฐ ๋ฑ๋ก
// AddToRoot / RemoveFromRoot ์ฌ์ฉ
UMyObject* ImportantObj = NewObject<UMyObject>();
ImportantObj->AddToRoot(); // Root Set์ ์ถ๊ฐ, GC ๋ณดํธ
// ๋ ์ด์ ํ์ ์์ ๋
ImportantObj->RemoveFromRoot(); // Root Set์์ ์ ๊ฑฐ, GC ๋์
// Root Set ์ฌ๋ถ ํ์ธ
bool bIsRoot = ImportantObj->IsRooted();
AddToRoot()๋ ํด๋น ๊ฐ์ฒด๋ฅผ ์๊ตฌ์ ์ผ๋ก GC์์ ๋ณดํธํฉ๋๋ค. RemoveFromRoot()๋ฅผ ํธ์ถํ์ง ์์ผ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํฉ๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ UPROPERTY ์ฐธ์กฐ๋ง์ผ๋ก ์ถฉ๋ถํ๋ฉฐ, AddToRoot๋ ๊ธ๋ก๋ฒ ๋งค๋์ ๊ฐ์ ํน์ํ ๊ฒฝ์ฐ์๋ง ์ฌ์ฉํด์ผ ํฉ๋๋ค.
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์์๋ TObjectPtr๋ฅผ ํตํ ์ฐธ์กฐ ๋ณ๊ฒฝ์ด ์ค์๊ฐ์ผ๋ก ์ถ์ ๋ฉ๋๋ค. Incremental GC ๋ชจ๋์์ GC ์ฌ์ดํด ์ค๊ฐ์ ์๋ก์ด ์ฐธ์กฐ๊ฐ ์๊ธฐ๋ฉด, TObjectPtr์ ์ฐ๊ธฐ ๋ฐฐ๋ฆฌ์ด๊ฐ ํด๋น ๊ฐ์ฒด๋ฅผ ์ฆ์ Reachable๋ก ๋งํนํ์ฌ ์๋ชป๋ ์์ง์ ๋ฐฉ์งํฉ๋๋ค.
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() ๊ฐ์ ํจ์ ํธ์ถ |
| ๋ณ๋ ฌํ | ์ฉ์ด | ์ด๋ ค์ |
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 ๋จ๊ณ๋ก ์งํ (๋ค์ ๊ฐ์)
Mark ๋จ๊ณ์์๋ FGCCSyncObject๋ฅผ ํตํด GC Lock์ ํ๋ํฉ๋๋ค. ์ด Lock์ด ํ์ฑํ๋ ๋์์๋ ๋ค๋ฅธ ์ค๋ ๋์์ UObject๋ฅผ ์์ฑํ๊ฑฐ๋ ํ๊ดดํ ์ ์์ต๋๋ค. IsGarbageCollecting()์ผ๋ก ํ์ฌ GC๊ฐ ์งํ ์ค์ธ์ง ํ์ธํ ์ ์์ต๋๋ค.
ํต์ฌ ์์ฝ
- Root Set์ AddToRoot, CDO, RF_Standalone, FGCObject ๋ฑ์ผ๋ก ๊ตฌ์ฑ๋๋ฉฐ GC์ ์์์ ์ ๋๋ค
- Mark ๋จ๊ณ๋ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ Unreachable๋ก ์ด๊ธฐํํ ํ, Root Set์์ BFS๋ก Reachable ๊ฐ์ฒด๋ฅผ ๋งํนํฉ๋๋ค
- TFastReferenceCollector๋ ํ ํฐ ์คํธ๋ฆผ ๊ธฐ๋ฐ์ผ๋ก ์ฐธ์กฐ๋ฅผ ์์งํ๋ฉฐ, ๊ฐ์ ํจ์ ํธ์ถ ์์ด ๋น ๋ฅด๊ฒ ๋์ํฉ๋๋ค
- UE5์ TObjectPtr๋ ์ฐ๊ธฐ ๋ฐฐ๋ฆฌ์ด๋ฅผ ํตํด Incremental GC ์ค ์ ์ฐธ์กฐ๋ฅผ ์ค์๊ฐ ์ถ์ ํฉ๋๋ค
- GC Lock์ Mark ๋จ๊ณ ๋์ ๋ค๋ฅธ ์ค๋ ๋์ UObject ์กฐ์์ ์ฐจ๋จํ์ฌ ์ผ๊ด์ฑ์ ๋ณด์ฅํฉ๋๋ค
๋์ ๊ณผ์
๋ฐฐ์ด ๋ด์ฉ์ ์ง์ ์ค์ตํด๋ณด์ธ์
์ฝ์ ๋ช ๋ น์ด obj list -gc๋ฅผ ์คํํ์ฌ ํ์ฌ Root Set์ ํฌํจ๋ ๊ฐ์ฒด ๋ชฉ๋ก์ ํ์ธํ์ธ์. AddToRoot()๋ก ์ปค์คํ UObject๋ฅผ Root Set์ ์ถ๊ฐํ ํ ๋ชฉ๋ก ๋ณํ๋ฅผ ๊ด์ฐฐํ๊ณ , RemoveFromRoot()๋ก ํด์ ํ GC ์์ง ์ฌ๋ถ๋ฅผ ํ ์คํธํ์ธ์.
5๊ฐ์ UObject๋ฅผ ์ฒด์ธ ํํ๋ก ์ฐธ์กฐ์ํค๊ณ (A->B->C->D->E), ์ค๊ฐ ๋งํฌ(C์ ์ฐธ์กฐ)๋ฅผ nullptr๋ก ์ค์ ํ ํ ForceGarbageCollection()์ ํธ์ถํ์ธ์. ์ด๋ค ๊ฐ์ฒด๊ฐ ์์ง๋๋์ง BeginDestroy ์ค๋ฒ๋ผ์ด๋ ๋ก๊ทธ๋ก ํ์ธํ์ธ์.
๋๋์ UObject(10,000๊ฐ ์ด์)๋ฅผ ๋ค์ํ ์ฐธ์กฐ ํจํด(์ ํ, ํธ๋ฆฌ, ๊ทธ๋ํ)์ผ๋ก ์์ฑํ๊ณ , stat gc ๋ช ๋ น์ด๋ก Mark ๋จ๊ณ ์์ ์๊ฐ์ ์ธก์ ํ์ธ์. ์ฐธ์กฐ ๊ตฌ์กฐ์ ๋ฐ๋ฅธ Mark ์๊ฐ ์ฐจ์ด๋ฅผ ๋ถ์ ๋ณด๊ณ ์๋ก ์์ฑํ์ธ์.