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

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

Unreachable ๊ฐ์ฒด ์ฒ˜๋ฆฌ, BeginDestroy/FinishDestroy ํ๋ฆ„, PendingKill/MarkAsGarbage๋ฅผ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค

SECTION 01

Unreachable ๊ฐ์ฒด ์ˆ˜์ง‘

Mark ๋‹จ๊ณ„ ์ดํ›„ ๋„๋‹ฌ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ๊ณผ์ •

Sweep ๋‹จ๊ณ„์˜ ์‹œ์ž‘

Mark ๋‹จ๊ณ„๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด EInternalObjectFlags::Unreachable ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚จ์€ ๊ฐ์ฒด๊ฐ€ ๊ฐ€๋น„์ง€์ž…๋‹ˆ๋‹ค. Sweep ๋‹จ๊ณ„์—์„œ๋Š” ์ด ๊ฐ์ฒด๋“ค์„ GUObjectArray๋ฅผ ์ˆœํšŒํ•˜๋ฉฐ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค.

Sweep ํ๋ฆ„ UnhashUnreachableObjects() โ”‚ โ”œโ”€โ”€ GUObjectArray ์ˆœํšŒ (DisregardForGC ์ดํ›„ ์˜์—ญ) โ”‚ โ””โ”€โ”€ for (each FUObjectItem with Unreachable flag) โ”‚ โ”œโ”€โ”€ ํ•ด์‹œ ํ…Œ์ด๋ธ”์—์„œ ์ œ๊ฑฐ โ”‚ โ”œโ”€โ”€ ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ๋ถ„๋ฆฌ โ”‚ โ””โ”€โ”€ GarbageObjects ๋ชฉ๋ก์— ์ถ”๊ฐ€ โ”‚ โ”œโ”€โ”€ NotifyUnreachableObjects() โ”‚ โ””โ”€โ”€ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋ฅผ ํ†ตํ•œ ์•Œ๋ฆผ โ”‚ โ””โ”€โ”€ GarbageObjects ๋ชฉ๋ก โ†’ ์†Œ๋ฉธ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ // ์†Œ๋ฉธ ํŒŒ์ดํ”„๋ผ์ธ for (each UnreachableObj) { UnreachableObj->ConditionalBeginDestroy(); }
๋น„๋™๊ธฐ ์†Œ๋ฉธ์˜ ์ด์œ 

UObject์˜ ์†Œ๋ฉธ์ด ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ๋‚˜๋‰œ ์ด์œ ๋Š” ๋ Œ๋”๋ง ๋ฆฌ์†Œ์Šค์™€ ๊ฐ™์€ ๋น„๋™๊ธฐ ์ž์› ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. GPU์—์„œ ์‚ฌ์šฉ ์ค‘์ธ ๋ฆฌ์†Œ์Šค๋ฅผ ์ฆ‰์‹œ ํ•ด์ œํ•˜๋ฉด ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, BeginDestroy์—์„œ ํ•ด์ œ ์š”์ฒญ์„ ํ•˜๊ณ  FinishDestroy์—์„œ ์‹ค์ œ ํ•ด์ œ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

SECTION 02

BeginDestroy์™€ FinishDestroy

UObject ์†Œ๋ฉธ์˜ ๋‘ ๋‹จ๊ณ„ ํ”„๋กœ์„ธ์Šค

์†Œ๋ฉธ ํŒŒ์ดํ”„๋ผ์ธ ์ƒ์„ธ

C++ // BeginDestroy - ์†Œ๋ฉธ ์‹œ์ž‘ (์˜ค๋ฒ„๋ผ์ด๋“œ ๊ฐ€๋Šฅ) void UMyObject::BeginDestroy() { // 1. ๋น„๋™๊ธฐ ๋ฆฌ์†Œ์Šค ํ•ด์ œ ์š”์ฒญ if (RenderResource) { RenderResource->ReleaseResource(); // GPU ๋ฆฌ์†Œ์Šค๋Š” ๋‹ค์Œ ํ”„๋ ˆ์ž„๊นŒ์ง€ ์œ ํšจ } // 2. ํƒ€์ด๋จธ, ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ ํ•ด์ œ if (UWorld* World = GetWorld()) { World->GetTimerManager().ClearAllTimersForObject(this); } // 3. ๋ถ€๋ชจ ํ˜ธ์ถœ (ํ•„์ˆ˜!) Super::BeginDestroy(); } // IsReadyForFinishDestroy - ์™„๋ฃŒ ํ™•์ธ bool UMyObject::IsReadyForFinishDestroy() { // ๋น„๋™๊ธฐ ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ bool bReady = Super::IsReadyForFinishDestroy(); bReady &= !RenderResource || RenderResource->IsReleased(); return bReady; } // FinishDestroy - ์ตœ์ข… ์ •๋ฆฌ void UMyObject::FinishDestroy() { // ์ด ์‹œ์ ์—์„œ ๋ชจ๋“  ๋น„๋™๊ธฐ ์ž‘์—…์ด ์™„๋ฃŒ๋จ delete RenderResource; RenderResource = nullptr; Super::FinishDestroy(); // ์ดํ›„ C++ ์†Œ๋ฉธ์ž ํ˜ธ์ถœ, ๋ฉ”๋ชจ๋ฆฌ ๋ฐ˜ํ™˜ }

์†Œ๋ฉธ ๋‹จ๊ณ„ ํƒ€์ด๋ฐ

๋‹จ๊ณ„ ์‹œ์  ์šฉ๋„
ConditionalBeginDestroy GC Sweep ์ฆ‰์‹œ ์†Œ๋ฉธ ์‹œ์ž‘ ์กฐ๊ฑด ํ™•์ธ
BeginDestroy ๊ฐ™์€ ํ”„๋ ˆ์ž„ ๋น„๋™๊ธฐ ๋ฆฌ์†Œ์Šค ํ•ด์ œ ์š”์ฒญ
IsReadyForFinishDestroy ๋งค ํ”„๋ ˆ์ž„ ํด๋ง ๋น„๋™๊ธฐ ์ž‘์—… ์™„๋ฃŒ ํ™•์ธ
FinishDestroy Ready ํ›„ ๋‹ค์Œ GC ์ตœ์ข… ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ
~UObject() FinishDestroy ์งํ›„ C++ ์†Œ๋ฉธ์ž
SECTION 03

PendingKill๊ณผ MarkAsGarbage

UE4์—์„œ UE5๋กœ์˜ ๊ฐ์ฒด ๋งˆํ‚น ๋ฐฉ์‹ ๋ณ€ํ™”

PendingKill์˜ ์ง„ํ™”

UE4์—์„œ ์‚ฌ์šฉ๋˜๋˜ PendingKill ์‹œ์Šคํ…œ์€ UE5์—์„œ MarkAsGarbage๋กœ ์ „ํ™˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ณ€๊ฒฝ์€ GC ์„ฑ๋Šฅ ํ–ฅ์ƒ๊ณผ TObjectPtr ๋„์ž…๊ณผ ๊ด€๋ จ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

C++ // UE4 ๋ฐฉ์‹ (Deprecated) // obj->MarkPendingKill(); // ๋” ์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š” // obj->IsPendingKill(); // Deprecated // UE5 ๋ฐฉ์‹ obj->MarkAsGarbage(); // ๊ฐ€๋น„์ง€๋กœ ๋งˆํ‚น // ์œ ํšจ์„ฑ ํ™•์ธ if (IsValid(obj)) // nullptr + Garbage ์ฒดํฌ { // ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ } // gc.PendingKillEnabled ์ฝ˜์†” ๋ณ€์ˆ˜ // 0 (๊ธฐ๋ณธ): MarkAsGarbage ๋ฐฉ์‹ ์‚ฌ์šฉ // 1: ๋ ˆ๊ฑฐ์‹œ PendingKill ๋ฐฉ์‹ (ํ˜ธํ™˜์„ฑ) // TObjectPtr์˜ ์ž๋™ null ์ฒ˜๋ฆฌ UPROPERTY() TObjectPtr<AActor> MyActor; MyActor->Destroy(); // MarkAsGarbage + ์†Œ๋ฉธ ์š”์ฒญ // ์ดํ›„ MyActor๋Š” ์ž๋™์œผ๋กœ null์ฒ˜๋Ÿผ ๋™์ž‘ (TObjectPtr) // raw UObject*์—์„œ๋Š” ์ง์ ‘ ํ™•์ธ ํ•„์š”
๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ฃผ์˜์‚ฌํ•ญ

UE4 ํ”„๋กœ์ ํŠธ๋ฅผ UE5๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•  ๋•Œ:

  • IsPendingKill() โ†’ !IsValid(obj)๋กœ ๋ณ€๊ฒฝ
  • MarkPendingKill() โ†’ MarkAsGarbage()๋กœ ๋ณ€๊ฒฝ
  • UObject* โ†’ TObjectPtr<T>๋กœ ๋ณ€๊ฒฝ ๊ถŒ์žฅ
  • gc.PendingKillEnabled=1๋กœ ์ž„์‹œ ํ˜ธํ™˜ ๊ฐ€๋Šฅ
SECTION 04

Incremental Sweep๊ณผ ๋ถ„์‚ฐ ์ฒ˜๋ฆฌ

Sweep ๋‹จ๊ณ„์˜ ์‹œ๊ฐ„ ๋ถ„์‚ฐ ์ „๋žต

์ ์ง„์  ์†Œ๋ฉธ ์ฒ˜๋ฆฌ

๋Œ€๋Ÿ‰์˜ ๊ฐ์ฒด๊ฐ€ ๋™์‹œ์— Unreachable๋กœ ํŒ์ •๋˜๋ฉด, ํ•œ ํ”„๋ ˆ์ž„์—์„œ ๋ชจ๋“  ๊ฐ์ฒด์˜ BeginDestroy๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์‹ฌ๊ฐํ•œ ํ”„๋ ˆ์ž„ ํžˆ์น˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. UE5๋Š” ์ด๋ฅผ ์—ฌ๋Ÿฌ ํ”„๋ ˆ์ž„์— ๋ถ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

๋ถ„์‚ฐ ์†Œ๋ฉธ // IncrementalDestroyGarbage ํ•จ์ˆ˜ (๋งค Tick ํ˜ธ์ถœ) bool IncrementalDestroyGarbage(bool bUseTimeLimit, float TimeLimit) { while (GGCObjectsPendingDestruction.Num() > 0) { UObject* Obj = GGCObjectsPendingDestruction.Pop(); if (Obj->IsReadyForFinishDestroy()) { Obj->ConditionalFinishDestroy(); // ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ } else { // ์•„์ง ์ค€๋น„ ์•ˆ ๋จ โ†’ ๋‹ค์‹œ ํ์— GGCObjectsPendingDestructionLater.Add(Obj); } // ์‹œ๊ฐ„ ์ œํ•œ ์ฒดํฌ if (bUseTimeLimit && FPlatformTime::Seconds() > TimeLimit) return false; // ๋‹ค์Œ ํ”„๋ ˆ์ž„์— ๊ณ„์† } return true; // ์™„๋ฃŒ }
FinishDestroy ์ง€์—ฐ์˜ ์˜ํ–ฅ

IsReadyForFinishDestroy()๊ฐ€ false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ด๋‹น ๊ฐ์ฒด๋Š” ๋‹ค์Œ ํ”„๋ ˆ์ž„์œผ๋กœ ์—ฐ๊ธฐ๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ GUObjectArray์˜ ์Šฌ๋กฏ์€ ์•„์ง ํ•ด์ œ๋˜์ง€ ์•Š์€ ์ƒํƒœ์ด๋ฉฐ, ์ƒˆ ๊ฐ์ฒด์— ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ณผ๋„ํ•œ ์ง€์—ฐ์€ GUObjectArray ์ธ๋ฑ์Šค ๊ณ ๊ฐˆ์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SUMMARY

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

์ด ๊ฐ•์˜์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ
  • Sweep ๋‹จ๊ณ„๋Š” Unreachable ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚จ์€ ๊ฐ์ฒด๋ฅผ GUObjectArray์—์„œ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค
  • UObject ์†Œ๋ฉธ์€ BeginDestroy โ†’ IsReadyForFinishDestroy โ†’ FinishDestroy์˜ ๋น„๋™๊ธฐ ํŒŒ์ดํ”„๋ผ์ธ์ž…๋‹ˆ๋‹ค
  • UE5์—์„œ PendingKill์€ MarkAsGarbage๋กœ ๋Œ€์ฒด๋˜์—ˆ์œผ๋ฉฐ, TObjectPtr๊ฐ€ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค
  • IncrementalDestroyGarbage๋Š” ์†Œ๋ฉธ ์ž‘์—…์„ ์—ฌ๋Ÿฌ ํ”„๋ ˆ์ž„์— ๋ถ„์‚ฐํ•˜์—ฌ ํžˆ์น˜๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค
  • BeginDestroy์—์„œ Super::BeginDestroy() ํ˜ธ์ถœ์„ ์žŠ์œผ๋ฉด ๊ฐ์ฒด๊ฐ€ ์˜๊ตฌ์ ์œผ๋กœ ์†Œ๋ฉธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
PRACTICE

๋„์ „ ๊ณผ์ œ

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

์‹ค์Šต 1: BeginDestroy/FinishDestroy ํ๋ฆ„ ๊ด€์ฐฐ

UObject๋ฅผ ์ƒ์†๋ฐ›์•„ BeginDestroy, IsReadyForFinishDestroy, FinishDestroy๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•˜์„ธ์š”. ๊ฐ ํ•จ์ˆ˜์— ๋กœ๊ทธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  GC ํŠธ๋ฆฌ๊ฑฐ ํ›„ ํ˜ธ์ถœ ์ˆœ์„œ์™€ ํƒ€์ด๋ฐ์„ ํ™•์ธํ•˜์„ธ์š”. IsReadyForFinishDestroy์—์„œ false๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

์‹ค์Šต 2: MarkAsGarbage vs Destroy ๋น„๊ต

AActor์—์„œ Destroy()์™€ MarkAsGarbage()๋ฅผ ๊ฐ๊ฐ ํ˜ธ์ถœํ•œ ํ›„, IsValid(), IsPendingKillPending(), HasAnyFlags(RF_BeginDestroyed) ๋“ฑ์˜ ์ƒํƒœ๋ฅผ ํ”„๋ ˆ์ž„๋ณ„๋กœ ๋กœ๊ทธ์— ์ถœ๋ ฅํ•˜์—ฌ ์†Œ๋ฉธ ๋‹จ๊ณ„ ์ฐจ์ด๋ฅผ ๋น„๊ตํ•˜์„ธ์š”.

์‹ฌํ™” ๊ณผ์ œ: Sweep ๋‹จ๊ณ„ ์„ฑ๋Šฅ ๋ถ„์„

๋‹ค์–‘ํ•œ ์ˆ˜์˜ Unreachable ๊ฐ์ฒด(100, 1000, 10000)๋ฅผ ์˜๋„์ ์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ  ์ฐธ์กฐ๋ฅผ ํ•ด์ œํ•œ ํ›„, stat gc๋กœ Sweep ๋‹จ๊ณ„ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜์„ธ์š”. ๊ฐ์ฒด ์ˆ˜ ๋Œ€๋น„ Sweep ์‹œ๊ฐ„์˜ ์„ ํ˜•์„ฑ์„ ๋ถ„์„ํ•˜๊ณ  gc.MaxObjectsToDestroyPerFrame์˜ ์˜ํ–ฅ์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.