PART 5 · 강의 2/4

객체 풀링과 GC

Object Pooling으로 GC 부하를 줄이고 객체 생성/파괴 비용을 제거하는 전략을 학습합니다

SECTION 01

Object Pooling 기본 개념

객체를 재사용하여 GC 오버헤드를 제거하는 기법

풀링의 GC 이점

Object Pooling은 객체를 파괴하지 않고 비활성화한 채 보관했다가 필요 시 재활성화합니다. GC 관점에서 객체가 파괴되지 않으므로 GC Sweep 비용이 발생하지 않습니다.

비교 // 풀링 없이: 매번 생성/파괴 SpawnProjectile() → NewObject + GUObjectArray 등록 DestroyProjectile() → MarkAsGarbage → BeginDestroy → GC // 초당 100발 = 초당 100번 GC 작업 // 풀링 사용: 재활성화/비활성화 GetFromPool() → SetActorHiddenInGame(false) + 위치 설정 ReturnToPool() → SetActorHiddenInGame(true) + 비활성화 // GC 작업 = 0
SECTION 02

Actor 풀링 구현

AActor 기반 객체 풀의 실전 구현

기본 Actor Pool

C++ UCLASS() class UActorPool : public UObject { GENERATED_BODY() public: void InitPool(UWorld* World, TSubclassOf<AActor> Class, int32 PoolSize) { for (int32 i = 0; i < PoolSize; i++) { AActor* Actor = World->SpawnActor<AActor>(Class); DeactivateActor(Actor); InactivePool.Add(Actor); } } AActor* GetFromPool(FVector Location, FRotator Rotation) { if (InactivePool.Num() == 0) return nullptr; AActor* Actor = InactivePool.Pop(); ActivateActor(Actor, Location, Rotation); ActivePool.Add(Actor); return Actor; } void ReturnToPool(AActor* Actor) { ActivePool.Remove(Actor); DeactivateActor(Actor); InactivePool.Add(Actor); } private: void DeactivateActor(AActor* Actor) { Actor->SetActorHiddenInGame(true); Actor->SetActorEnableCollision(false); Actor->SetActorTickEnabled(false); Actor->SetActorLocation(FVector(0, 0, -10000)); } void ActivateActor(AActor* A, FVector Loc, FRotator Rot) { A->SetActorLocationAndRotation(Loc, Rot); A->SetActorHiddenInGame(false); A->SetActorEnableCollision(true); A->SetActorTickEnabled(true); } UPROPERTY() TArray<AActor*> ActivePool; UPROPERTY() TArray<AActor*> InactivePool; };
풀링과 GC 참조

풀 내의 비활성 Actor들은 UPROPERTY TArray에 의해 강한 참조되므로 GC에 의해 수집되지 않습니다. 이것이 풀링의 핵심 - GC 사이클에서 완전히 제외되며, 객체 수는 일정하게 유지됩니다.

SECTION 03

UObject 풀링 패턴

Actor가 아닌 일반 UObject의 풀링 기법

UObject Pool 구현

C++ template<typename T> class TUObjectPool { public: T* Acquire(UObject* Outer) { if (FreeList.Num() > 0) { T* Obj = FreeList.Pop(); Obj->OnAcquiredFromPool(); // 커스텀 재활성화 return Obj; } // 풀이 비면 새로 생성 return NewObject<T>(Outer); } void Release(T* Obj) { Obj->OnReturnedToPool(); // 커스텀 비활성화 FreeList.Add(Obj); } // GC 참조 유지 - FGCObject 또는 UPROPERTY 필요 TArray<T*>& GetFreeList() { return FreeList; } private: TArray<T*> FreeList; };
풀링 시 상태 초기화 필수

풀에서 꺼낸 객체는 이전 사용의 상태가 남아 있습니다. OnAcquiredFromPool()에서 모든 상태를 초기화해야 합니다. 특히 UPROPERTY 참조, 타이머, 델리게이트 바인딩을 반드시 정리하세요.

SECTION 04

풀링 성능 효과 측정

풀링 전후의 GC 성능 비교

성능 비교

시나리오 (초당 100회 생성/파괴)풀링 없음풀링 적용
GC Mark 시간2.5ms1.2ms
GC Sweep 시간1.8ms0.1ms
GC 사이클당 파괴 객체~6000~0
UObject 총 수 변동큰 변동일정
GC 히치 빈도높음거의 없음
풀링 대상 선정 기준

모든 객체를 풀링할 필요는 없습니다. 다음 기준으로 판단하세요:

  • 초당 10회 이상 생성/파괴되는 객체 (투사체, 파티클, 피격 효과)
  • 짧은 수명의 반복 생성 Actor (탄환, 파편)
  • 생성 비용이 높은 객체 (복잡한 컴포넌트 구성)
SUMMARY

핵심 요약

이 강의에서 배운 내용
  • Object Pooling은 객체를 비활성화하고 재사용하여 GC 비용을 제거합니다
  • 풀 내 객체는 UPROPERTY 참조로 GC에서 보호되어야 합니다
  • 풀에서 꺼낸 객체는 상태 초기화가 필수입니다
  • 초당 10회 이상 생성/파괴되는 객체가 풀링의 주요 대상입니다
  • 풀링 적용 시 GC Sweep 비용이 거의 0으로 감소합니다
PRACTICE

도전 과제

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

실습 1: 기본 오브젝트 풀 구현

TArray 기반의 간단한 UObject 풀을 구현하세요. Acquire/Release 인터페이스를 만들고, 풀 사용 시 vs 매번 NewObject 생성 시의 GC 부하를 stat gc로 비교하세요. 풀 크기에 따른 메모리 트레이드오프도 분석하세요.

실습 2: Actor 풀링 시스템 구현

프로젝타일이나 이펙트 Actor를 풀링하는 시스템을 구현하세요. SetActorHiddenInGame, SetActorEnableCollision, SetActorTickEnabled를 활용하여 비활성화/활성화 패턴을 만들고, Destroy 대신 풀 반환을 사용하세요.

심화 과제: 자동 크기 조절 오브젝트 풀

사용 패턴을 모니터링하여 자동으로 풀 크기를 확장/축소하는 적응형 풀링 시스템을 구현하세요. 최소/최대 풀 크기 제한, 미사용 객체의 시간 기반 정리, GC 부하에 따른 풀 크기 조절 등의 기능을 포함하세요.