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

Incremental GC

Time Slicing, gc.TimeLimitSeconds, ์ ์ง„์  ์ˆ˜์ง‘ ์ „๋žต์œผ๋กœ GC ํžˆ์น˜๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ธฐ๋ฒ•์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค

SECTION 01

Incremental GC ๊ฐœ์š”

์ ์ง„์  ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์˜ ํ•„์š”์„ฑ๊ณผ ๊ธฐ๋ณธ ์›๋ฆฌ

์™œ Incremental GC์ธ๊ฐ€?

๊ธฐ๋ณธ(Full) GC๋Š” ํ•œ ํ”„๋ ˆ์ž„์—์„œ ์ „์ฒด Mark-Sweep์„ ์™„๋ฃŒํ•ฉ๋‹ˆ๋‹ค. ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ์ˆ˜์‹ญ๋งŒ ๊ฐœ์˜ UObject๊ฐ€ ์žˆ์œผ๋ฉด ์ˆ˜์‹ญ ms์˜ ํžˆ์น˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. Incremental GC๋Š” ์ด ์ž‘์—…์„ ์—ฌ๋Ÿฌ ํ”„๋ ˆ์ž„์— ๋ถ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.

๋น„๊ต // Full GC (๊ธฐ๋ณธ) Frame N: [Game Logic][====== Full GC (30ms) ======][Render] // ๊ฒฐ๊ณผ: 30ms ํžˆ์น˜ โ†’ ํ”„๋ ˆ์ž„ ๋“œ๋กญ // Incremental GC Frame N: [Game Logic][GC 2ms][Render] Frame N+1: [Game Logic][GC 2ms][Render] Frame N+2: [Game Logic][GC 2ms][Render] ... Frame N+14:[Game Logic][GC 2ms][Render] // ๊ฒฐ๊ณผ: ๊ฐ ํ”„๋ ˆ์ž„ 2ms โ†’ ํžˆ์น˜ ์—†์Œ
ํ™œ์„ฑํ™” ๋ฐฉ๋ฒ•

Incremental GC๋Š” gc.AllowIncrementalReachability=1๋กœ ํ™œ์„ฑํ™”๋˜๋ฉฐ UE5์—์„œ๋Š” ๊ธฐ๋ณธ ํ™œ์„ฑํ™”์ž…๋‹ˆ๋‹ค. gc.TimeLimitSeconds๋กœ ํ”„๋ ˆ์ž„๋‹น ์ตœ๋Œ€ GC ์‹œ๊ฐ„์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

SECTION 02

Time Slicing ๋ฉ”์ปค๋‹ˆ์ฆ˜

ํ”„๋ ˆ์ž„ ์‹œ๊ฐ„ ์˜ˆ์‚ฐ ๋‚ด์—์„œ GC ์ž‘์—…์„ ๋ถ„๋ฐฐํ•˜๋Š” ๋ฐฉ๋ฒ•

gc.TimeLimitSeconds

ํ•ต์‹ฌ ์ฝ˜์†” ๋ณ€์ˆ˜์ธ gc.TimeLimitSeconds๋Š” ํ•œ ํ”„๋ ˆ์ž„์—์„œ GC๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ์‹œ๊ฐ„(์ดˆ)์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์ด ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜๋ฉด ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ ์ด์–ด์„œ ์ž‘์—…ํ•ฉ๋‹ˆ๋‹ค.

C++ // ์ฃผ์š” GC ํƒ€์ด๋ฐ ์ฝ˜์†” ๋ณ€์ˆ˜ gc.TimeLimitSeconds = 0.002 // ํ”„๋ ˆ์ž„๋‹น GC ์‹œ๊ฐ„ ์ œํ•œ (2ms) gc.TimeBetweenPurgingPendingKillObjects = 60.0 // GC ๊ฐ„๊ฒฉ (์ดˆ) gc.AllowIncrementalReachability = 1 // Incremental ํ™œ์„ฑํ™” gc.IncrementalBeginDestroyEnabled = 1 // BeginDestroy๋„ ์ ์ง„์  // Incremental Reachability ๋‚ด๋ถ€ (๊ฐ„๋žตํ™”) bool PerformIncrementalReachabilityAnalysis(float TimeLimit) { double StartTime = FPlatformTime::Seconds(); while (ObjectsToProcess.Num() > 0) { UObject* Current = ObjectsToProcess.Pop(); ProcessObjectReferences(Current); // ์‹œ๊ฐ„ ์ œํ•œ ์ฒดํฌ if (FPlatformTime::Seconds() - StartTime > TimeLimit) { // ํ˜„์žฌ ์ƒํƒœ ์ €์žฅ, ๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ ๊ณ„์† return false; // ๋ฏธ์™„๋ฃŒ } } return true; // ์™„๋ฃŒ }

๊ถŒ์žฅ TimeLimitSeconds ๊ฐ’

ํ”Œ๋žซํผ ๋ชฉํ‘œ FPS ๊ถŒ์žฅ ๊ฐ’ ํ”„๋ ˆ์ž„ ์˜ˆ์‚ฐ ๋น„์œจ
PC (60fps) 60 FPS 0.002 (2ms) ~12%
PC (120fps) 120 FPS 0.001 (1ms) ~12%
์ฝ˜์†” (30fps) 30 FPS 0.004 (4ms) ~12%
๋ชจ๋ฐ”์ผ 30-60 FPS 0.003 (3ms) ~10-18%
SECTION 03

์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด์™€ ์ฐธ์กฐ ์•ˆ์ „์„ฑ

Incremental GC ์ค‘ ์ƒˆ๋กœ์šด ์ฐธ์กฐ๊ฐ€ ์ƒ๊ธธ ๋•Œ์˜ ์•ˆ์ „ ๋ณด์žฅ

TObjectPtr ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด

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

C++ // TObjectPtr์˜ ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด (๊ฐ„๋žตํ™”) template<typename T> class TObjectPtr { FObjectPtr ObjectPtr; void operator=(T* NewValue) { if (IsGarbageCollecting()) { // GC ์ง„ํ–‰ ์ค‘์ด๋ฉด ์ƒˆ ์ฐธ์กฐ ๋Œ€์ƒ์„ Reachable๋กœ ๋งˆํ‚น if (NewValue) { FUObjectItem* Item = GUObjectArray.ObjectToItem(NewValue); if (Item->HasAnyFlags(EInternalObjectFlags::Unreachable)) { Item->ClearFlags(EInternalObjectFlags::Unreachable); // ์žฌ์Šค์บ” ํ์— ์ถ”๊ฐ€ GGCReferenceProcessor.MarkForRescan(NewValue); } } } ObjectPtr = NewValue; } };
raw UObject*์˜ ์œ„ํ—˜

Incremental GC๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ์—์„œ raw UObject*๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด๊ฐ€ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด๋Š” GC๊ฐ€ ์ง„ํ–‰ ์ค‘์ผ ๋•Œ ์ƒˆ๋กœ ์ฐธ์กฐ๋ฅผ ์„ค์ •ํ•ด๋„ ๋Œ€์ƒ ๊ฐ์ฒด๊ฐ€ Unreachable๋กœ ๋‚จ์•„ ์ž˜๋ชป ์ˆ˜์ง‘๋  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. UE5์—์„œ TObjectPtr ์‚ฌ์šฉ์ด ๊ฐ•๋ ฅํžˆ ๊ถŒ์žฅ๋˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค.

SECTION 04

Incremental GC ์„ค์ •๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง

ํ”„๋กœ์ ํŠธ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๋Š” Incremental GC ํŠœ๋‹

์ฃผ์š” ์„ค์ • ๋ณ€์ˆ˜

DefaultEngine.ini ; Incremental GC ๊ด€๋ จ ์„ค์ • [/Script/Engine.GarbageCollectionSettings] gc.TimeLimitSeconds=0.002 gc.AllowIncrementalReachability=True gc.IncrementalBeginDestroyEnabled=True gc.CreateGCClusters=True gc.TimeBetweenPurgingPendingKillObjects=60.0 gc.MaxObjectsNotConsideredByGC=0 gc.MaxObjectsInGame=2162688 gc.MaxObjectsInEditor=12582912 gc.NumRetriesBeforeForcingGC=10 ; ๋Ÿฐํƒ€์ž„์—์„œ ๋ณ€๊ฒฝ ; ์ฝ˜์†”: gc.TimeLimitSeconds 0.003

Incremental GC ๋ชจ๋‹ˆํ„ฐ๋ง

์ฝ˜์†” ๋ช…๋ น์–ด // GC ํ†ต๊ณ„ ํ‘œ์‹œ stat gc // ์ถœ๋ ฅ ์˜ˆ์‹œ: GC Total Time: 2.1 ms โ† ์ด๋ฒˆ ํ”„๋ ˆ์ž„ GC ์‹œ๊ฐ„ Mark Time: 1.5 ms โ† Mark ๋‹จ๊ณ„ ์†Œ์š” Sweep Time: 0.6 ms โ† Sweep ๋‹จ๊ณ„ ์†Œ์š” Objects Destroyed: 42 โ† ํŒŒ๊ดด๋œ ๊ฐ์ฒด ์ˆ˜ Incremental: Yes โ† Incremental ๋ชจ๋“œ ์—ฌ๋ถ€ Remaining Objects: 1523 โ† ์•„์ง ์ฒ˜๋ฆฌ ์•ˆ ๋œ ๊ฐ์ฒด
Incremental GC๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

๊ฐ์ฒด ์ƒ์„ฑ ์†๋„๊ฐ€ GC ์ฒ˜๋ฆฌ ์†๋„๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด Incremental GC๊ฐ€ ์˜์›ํžˆ ์™„๋ฃŒ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์—”์ง„์€ gc.NumRetriesBeforeForcingGC ํšŸ์ˆ˜๋งŒํผ ์‹œ๋„ ํ›„ ๊ฐ•์ œ Full GC๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด ๊ฐ์ฒด ์ƒ์„ฑ ์†๋„๋ฅผ ์ œ์–ดํ•˜๊ฑฐ๋‚˜ Object Pooling์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

SUMMARY

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

์ด ๊ฐ•์˜์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ
  • Incremental GC๋Š” Mark-Sweep ์ž‘์—…์„ ์—ฌ๋Ÿฌ ํ”„๋ ˆ์ž„์— ๋ถ„์‚ฐํ•˜์—ฌ ํžˆ์น˜๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค
  • gc.TimeLimitSeconds๋กœ ํ”„๋ ˆ์ž„๋‹น GC ์‹œ๊ฐ„ ์˜ˆ์‚ฐ์„ ์„ค์ •ํ•˜๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ 2ms๊ฐ€ ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค
  • TObjectPtr์˜ ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด๊ฐ€ Incremental GC ์ค‘ ์ƒˆ ์ฐธ์กฐ์˜ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค
  • raw UObject*๋Š” ์“ฐ๊ธฐ ๋ฐฐ๋ฆฌ์–ด๊ฐ€ ์—†์œผ๋ฏ€๋กœ Incremental GC์—์„œ ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค
  • ๊ฐ์ฒด ์ƒ์„ฑ์ด ๊ณผ๋„ํ•˜๋ฉด ๊ฐ•์ œ Full GC๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ Object Pooling์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
PRACTICE

๋„์ „ ๊ณผ์ œ

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

์‹ค์Šต 1: gc.TimeLimitSeconds ์กฐ์ ˆ ์‹คํ—˜

์ฝ˜์†”์—์„œ gc.TimeLimitSeconds๋ฅผ 0.001, 0.005, 0.01๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ GC๊ฐ€ ํ•œ ํ”„๋ ˆ์ž„์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ์ž‘์—…๋Ÿ‰ ๋ณ€ํ™”๋ฅผ stat gc๋กœ ๊ด€์ฐฐํ•˜์„ธ์š”. ๊ฐ ์„ค์ •์—์„œ GC๊ฐ€ ์™„๋ฃŒ๋˜๊ธฐ๊นŒ์ง€ ๊ฑธ๋ฆฌ๋Š” ์ด ํ”„๋ ˆ์ž„ ์ˆ˜๋ฅผ ๊ธฐ๋กํ•˜์„ธ์š”.

์‹ค์Šต 2: Incremental GC ํžˆ์น˜ ๋ชจ๋‹ˆํ„ฐ๋ง

stat unit๊ณผ stat gc๋ฅผ ๋™์‹œ์— ํ™œ์„ฑํ™”ํ•˜๊ณ , 10,000๊ฐœ์˜ UObject๋ฅผ ์ƒ์„ฑ ํ›„ ์ฐธ์กฐ ํ•ด์ œํ•˜์„ธ์š”. gc.IncrementalBeginDestroyEnabled=1๊ณผ =0์—์„œ์˜ ํ”„๋ ˆ์ž„ ํƒ€์ž„ ๋ณ€ํ™”๋ฅผ ๋น„๊ต ๊ทธ๋ž˜ํ”„๋กœ ๊ธฐ๋กํ•˜์„ธ์š”.

์‹ฌํ™” ๊ณผ์ œ: ์ปค์Šคํ…€ Time Budget GC ์ „๋žต ์„ค๊ณ„

๊ฒŒ์ž„ํ”Œ๋ ˆ์ด ์ƒํ™ฉ(์ „ํˆฌ ์ค‘, ๋กœ๋”ฉ ์ค‘, ์ผ์‹œ์ •์ง€)์— ๋”ฐ๋ผ gc.TimeLimitSeconds๋ฅผ ๋™์ ์œผ๋กœ ์กฐ์ ˆํ•˜๋Š” GC Manager ํด๋ž˜์Šค๋ฅผ ์„ค๊ณ„ํ•˜์„ธ์š”. ์ „ํˆฌ ์ค‘์—๋Š” ์˜ˆ์‚ฐ์„ ์ค„์ด๊ณ , ๋กœ๋”ฉ ํ™”๋ฉด์—์„œ๋Š” ForceGarbageCollection์„ ํ˜ธ์ถœํ•˜๋Š” ์ „๋žต์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.