프로그램에서 new, delete는 편리하지만 느립니다. 특히 작은 객체를 수없이 반복 생성/소멸하는 경우 성능 병목의 원인이 됩니다. 이럴 때 등장하는 해결책이 바로 메모리 풀(Pool Allocator) 입니다.
즉, malloc/free or new/delete의 호출 횟수를 대폭 줄이는 것이 핵심입니다.
class MemoryPool
{
public:
MemoryPool(int32 allocSize);
~MemoryPool();
void* Allocate();
void Release(void* ptr);
private:
void Push(SListEntry* entry);
SListEntry* Pop();
private:
int32 _allocSize = 0;
SListHeader _header = {};
};
allocSize: 할당받을 블록의 크기_header: SLIST 구조를 사용하여 메모리 블록을 스택처럼 관리MemoryPool::MemoryPool(int32 allocSize) : _allocSize(allocSize)
{
assert(allocSize >= sizeof(SListEntry));
::InitializeSListHead(&_header);
}
InitializeSListHead를 통해 _header 준비 완료void* MemoryPool::Allocate()
{
SListEntry* entry = Pop();
if (entry != nullptr)
return static_cast<void*>(entry);
return ::_aligned_malloc(_allocSize, SLIST_ALIGNMENT);
}
Pop()을 통해 재사용 가능한 블록이 있는지 확인aligned_malloc으로 생성void MemoryPool::Release(void* ptr)
{
Push(static_cast<SListEntry*>(ptr));
}
void MemoryPool::Push(SListEntry* entry)
{
::InterlockedPushEntrySList(&_header, entry);
}
SListEntry* MemoryPool::Pop()
{
return ::InterlockedPopEntrySList(&_header);
}
Interlocked 시리즈 함수는 SLIST에서 스레드 안전하게 Lock-Free Stack을 다루기 위한 함수MemoryPool::~MemoryPool()
{
while (true)
{
SListEntry* entry = Pop();
if (entry == nullptr)
break;
::_aligned_free(entry);
}
}
| 기능 | 설명 |
|---|---|
Allocate() | SLIST에서 꺼내거나 새로 할당 |
Release() | SLIST에 메모리 반환 |
Push()/Pop() | Interlocked 함수로 Lock-Free Stack 관리 |
_aligned_malloc | SLIST 호환을 위한 16바이트 정렬 필요 |
MemoryPool pool(4096);
vector<void*> v;
for (int32 i = 0; i < 100'0000; i++)
{
void* ptr = pool.Allocate();
v.push_back(ptr);
}
::shuffle(v.begin(), v.end(), mt19937(random_device()()));
for (void* ptr : v)
{
pool.Release(ptr);
}
new/delete 대신 메모리 풀을 활용하면 수배~수십 배 빠른 성능 확보 가능