Unity ObjectPool에 관하여
오브젝트 풀(Object Pool)이란, 프로그램 실행 중 실시간 동적 할당이 성능 저하의 주요 원인이 되므로, 미리 일정량의 객체를 생성해 두고 필요할 때 재사용하는 기법이다. 이를 통해 동적 할당에 소요되는 시간을 줄이고, 프로세스의 속도 저하를 완화할 수 있다.
다음과 같은 형식으로 정의 되었다.
publicObjectPool<T>(Func<T>createFunc, Action<T>actionOnGet, Action<T>actionOnRelease, Action<T>actionOnDestroy, boolcollectionCheck, intdefaultCapacity, intmaxSize);
Unity의 오브젝트 풀링
Unity에서는 UnityEngine.Pool 네임스페이스를 통해 오브젝트 풀 기능을 제공하며, 이는 제네릭(Generic) 타입으로 구현되어 있다. 내부적으로 List<T> 기반으로 작동하며, 객체의 할당과 반환은 각각 Get과 Release 메서드를 통해 이루어진다.
오브젝트 풀의 동작 방식
Get 메서드는 리스트의 마지막 인덱스에 위치한 객체를 반환하며, 이는 스택(stack) 방식으로 동작한다.
[ 스택 방식을 사용하는 이유 ]
1. 메모리 지역성(Locality of Reference) 극대화
- 가장 최근에 사용한 객체를 재사용함으로써, 해당 객체가 물리 메모리의 페이지 프레임에서 아직 해제되지 않았다면 추가적인 페이지 폴트(page fault)를 방지할 수 있다.
- 즉, 운영 체제의 메모리 관리 기법에 의해 페이지 아웃이 발생하지 않는 경우, 불필요한 디스크 I/O를 줄여 성능을 유지할 수 있다.
2. CPU 캐시 효율성 향상
- 최근에 사용한 객체를 다시 사용하면 CPU 캐시(L1, L2, L3 캐시)에 해당 데이터가 남아 있을 확률이 높아 캐시 히트율(cache hit rate)을 증가시킬 수 있다.
- 특히, 스택 방식은 "후입선출(LIFO)" 구조로 동작하므로, 가장 최근에 사용한 객체가 메모리 계층에서 더 높은 위치(L1/L2 캐시)에 남아 있을 가능성이 크다.
이를 통해 메모리 접근 속도가 빨라지고, 불필요한 메모리 계층 접근 비용을 절감할 수 있다.
List<T>의 동적 배열 특성 및 주의점
Unity의 오브젝트 풀은 내부적으로 C++의 vector와 유사한 동적 배열 구조를 사용한다.
리스트의 크기가 부족할 때마다 capacity(용량)를 자동으로 2배씩 증가시키는 방식으로 동작하는데, 이로 인해 너무 많은 객체를 사용한 후 할당된 메모리의 일부만 활용하는 경우 문제가 발생할 수 있다.
예를 들어, 실제로 사용하는 객체 수가 capacity의 절반보다 약간 많은 수준에서 멈춘다면, 불필요한 메모리 낭비가 발생할 수 있는 것이다.
[ 해결책 ]
1. 초기 용량(Initial Capacity) 설정
- 리스트를 생성할 때 예상 객체 수에 맞춰 적절한 초기 용량을 설정하여 불필요한 동적 확장을 방지할 수 있다.
Unity의 List<T>는 기본적으로 크기가 0에서 시작하고, 첫 번째 요소가 추가될 때 기본 용량(일반적으로 4 또는 8)으로 증가한 후, 필요 시 2배씩 확장된다.
따라서, 예측 가능한 최대 객체 수를 기반으로 초기 용량을 지정하면 동적 확장으로 인한 불필요한 메모리 복사 및 재할당을 줄일 수 있다.
- 이는 publicObjectPool의 마지막 인자인 intmaxSize 로 설정이 가능하다.
2. 사용 후 리스트 크기 조정 (TrimExcess 활용)
- List<T>의 TrimExcess 메서드는 불필요하게 할당된 여유 메모리를 해제하여 메모리 사용량을 최적화하는 데 활용할 수 있다.
- 객체 수가 급격히 줄어든 경우 TrimExcess를 호출하면 리스트의 capacity가 현재 요소 수에 맞게 조정된다. 그러나 이 과정에서 [ 새로운 메모리 할당 → 기존 데이터 복사 → 기존 메모리 해제 ]의 연산이 발생하므로, 빈번한 호출 시 메모리 절약 효과보다 오히려 성능 저하가 발생할 수 있다. 따라서, TrimExcess는 메모리 사용 패턴을 분석하여 적절한 시점에만 호출하는 것이 권장된다.
[ 주의점 ]
1. 최대 용량을 넘어서 객체 반환
- 오브젝트 풀이 설정된 최대 용량(maxSize)을 넘는 객체를 반환하려고 할 경우, 해당 객체는 파괴되어 더 이상 사용할 수 없게 된다.