프로그램에서 오브젝트를 생성하고 삭제하는 작업은 메모리 사용량이 늘어나는 문제와 성능 저하의 원인이 되기도 한다. 이는 오브젝트 생성이 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화 과정이 필요하고, 오브젝트가 파괴된다면 이후에 발생하는 가비지 컬렉팅으로 인한 문제이다. 이러한 생성/삭제 과정을 줄이기 위해 pool에 오브젝트들을 저장해둔 뒤 필요시 꺼내쓰는 Object Pooling
방식을 사용해보자.
오브젝트가 생성되기 위해선 메모리 heap 공간에 오브젝트를 저장할 메모리를 동적으로 할당받는다. 이때, 할당 받을 메모리 영역을 탐색하기 때문에 heap 메모리 영역을 탐색하는데 맍은 시간이 소요될 수 있다. 특히 메모리 크기가 크고 빈 메모리 영역이 뒷쪽에 있다면 매번 메모리 할당에 오버헤드가 발생한다. 또한 새로 생성되는 오브젝트의 초기화 과정에서 생성자 영역의 코드가 실행되며 리소스를 로드하는 등의 시간이 소요된다.
오브젝트가 삭제되기 위해서는 Unreal Engine의 GC가 실행되는데 이는 더 이상 필요하지 않은 객체를 가비지 컬렉터가 메모리에서 할당 해제해주는 방법이다. 따라서 Destroy
함수를 실행한다고 바로 메모리의 할당해제가 되는 것이 아니라 해당 오브젝트가 해제가능한지 확인 후 적절할 때 메모리를 해제하기 때문에 메모리 사용량이 증가하게 된다.
오브젝트를 반복적으로 생성/삭제하여 힙 메모리를 동적 할당/해제하게 되면, 힙 메모리에 동적 해제로 인한 빈 공간이 생기며 이 공간 보다 더 작은 프로세스가 핟당되며 메모리 틈에 사용하지 못하는 크기의 메모리가 생기게된다. 따라서 메모리 단편화가 발생하게 된다.
오브젝트 풀링은 위에서 말한 오브젝트의 반복적인 생성 및 삭제로 인한 문제를 해결하기 위한 디자인 패턴이다. 오브젝트 풀
에 필요한 수의 오브젝트
들을 미리 생성한 뒤 hidden
상태로 설정한다. 그 뒤 필요할 때 마다 오브젝트 풀
에서 꺼내쓰고, 사용이 끝났다면 오브젝트 풀
에 다시 넣어주는 방식이다.
public:
FORCEINLINE void SetObjectPool(class UObjectPool* InObjectPool) { ObjectPool = InObjectPool; }
void ReturnSelf();
void SetActive(bool IsActive);
FORCEINLINE bool IsActive() { return bActive; }
private:
bool bActive;
class UObjectPool* ObjectPool;
void AMyObject::SetActive(bool IsActive)
{
bActive = IsActive;
SetActorHiddenInGame(!bActive);
}
void AMyObject::ReturnSelf()
{
if(ObjectPool == nullptr) return;
ObjectPool->ReturnObject(this);
SetActive(false);
}
public:
class AMyObject* GetPooledObject();
void Expand();
void ReturnObject(class AMyObject* ReturnObject);
public:
TSubclassOf<class AMyObject> PooledObjectClass;
int32 PoolSize = 0;
int32 ExpandSize = 10;
private:
TArray<class AMyObject*> Pool;
AMyObject* UPool::GetPooledObject()
{
if(Pool.Num() == 0) Expand(); //추가
return Pool.Pop();
}
void UPool::Expand()
{
for(int i=0; i<ExpandSize; i++)
{
AMyObject* PoolableActor = GetWorld()->SpawnActor<AMyObject>(PooledObjectClass, FVector().ZeroVector, FRotator().ZeroRotator);
PoolableActor->SetActive(false);
Pool.Push(PoolableActor);
}
PoolSize += ExpandSize;
}
void UPool::ReturnObject(AMyObject* ReturnObject)
{
Pool.Push(ReturnObject);
}