티스토리 뷰

프로그래밍 언어/C++

MemoryPool #1

지현씨 2023. 4. 5. 21:33

 : 메모리 풀이란, 동적할당한 메모리들을 만들어 Pool에 저장한 후, 필요할 때 Pool에서 꺼내 사용하고 반납하는 형식을 말한다.

 

이런 방식을 사용하는 이유는 동적할당 함수, 즉 malloc이나 new을 호출하는 시간을 줄여, 속도 향상을 꾀하기 위해 사용합니다.

 

메모리 풀의 형태를 두 가지정도 배웠는데, 여기는 첫 번째 형태를 적어보겠습니다.

 

메모리 풀의 형태 [Header][Data]가 하나의 형태를 가집니다. 메모리 헤더를 다는 이유는 Data의 크기를 알아내기 위해 사용합니다. 그래서 Pool의 형태는 [Header][Data][Header][Data][Header][Data] 식으로, Header를 꺼내 사용하는 형식으로 되어있습니다.


메모리 Pool - Header

struct MemoryHeader
{
	// [MemoryHeader][Data]
	MemoryHeader(int32 size) : allocSize(size) { }

	static void* AttachHeader(MemoryHeader* header, int32 size)
	{
		new(header)MemoryHeader(size); // placement new
		return reinterpret_cast<void*>(++header);
	}

	static MemoryHeader* DetachHeader(void* ptr)
	{
		MemoryHeader* header = reinterpret_cast<MemoryHeader*>(ptr) - 1;
		return header;
	}

	int32 allocSize;
};

AttachHeader : Header뒤에 Data주소가 바로 붙어있기 때문에 ++를 해줌으로써 포인터를 Data 위치로 바꿔서 리턴

DetachHeader : Data의 주소를 받으면, - 1을 하여 포인터 특성을 활용, header포인터 위치로 옮겨서 리턴

 

메모리 Pool - Data

class MemoryPool
{
public:
	MemoryPool(int32 allocSize);
	~MemoryPool();

	void			Push(MemoryHeader* ptr);
	MemoryHeader*	Pop();

private:
	int32 _allocSize = 0;
	atomic<int32> _allocCount = 0;

	USE_LOCK;
	queue<MemoryHeader*> _queue;
};
#include "MemoryPool.h"

MemoryPool::MemoryPool(int32 allocSize) : _allocSize(allocSize)
{
}

MemoryPool::~MemoryPool()
{
	while (_queue.empty() == false)
	{
		MemoryHeader* header = _queue.front();
		_queue.pop();
		::free(header);
	}
}

void MemoryPool::Push(MemoryHeader* ptr)
{
	// LOCK
	ptr->allocSize = 0;

	// Pool에 메모리 반납
	_queue.push(ptr);

	_allocCount.fetch_sub(1);
}

MemoryHeader* MemoryPool::Pop()
{
	MemoryHeader* header = nullptr;

	{
		// LOCK
		// Pool에 여분이 있는지?
		if (_queue.empty() == false)
		{
			// 있으면 하나 꺼내온다
			header = _queue.front();
			_queue.pop();
		}
	}

	// 없으면 새로 만들다
	if (header == nullptr)
	{
		header = reinterpret_cast<MemoryHeader*>(::malloc(_allocSize));
	}
	else
	{
		ASSERT_CRASH(header->allocSize == 0);
	}

	_allocCount.fetch_add(1);

	return header;
}

아래는 메로리 Pool을 사용하는 코드입니다. 코드가 길기 때문에 보실분들은 펼쳐셔 보시면 됩니다.

#pragma once
#include "Allocator.h"

class MemoryPool;

/*-------------
	Memory
---------------*/
#define xalloc(size)		PoolAllocator::Alloc(size)
#define xrelease(ptr)		PoolAllocator::Release(ptr)

class Memory
{
	enum
	{
		// ~1024까지 32단위, ~2048까지 128단위, ~4096까지 256단위
		POOL_COUNT = (1024 / 32) + (1024 / 128) + (2048 / 256),
		MAX_ALLOC_SIZE = 4096
	};

public:
	Memory();
	~Memory();

	void*	Allocate(int32 size);
	void	Release(void* ptr);

private:
	vector<MemoryPool*> _pools;

	// 메모리 크기 <-> 메모리 풀
	// O(1) 빠르게 찾기 위한 테이블
	MemoryPool* _poolTable[MAX_ALLOC_SIZE + 1];
};


template<typename Type, typename... Args>
Type* xnew(Args&&... args)
{
	Type* memory = static_cast<Type*>(xalloc(sizeof(Type)));
	new(memory)Type(forward<Args>(args)...); // placement new
	return memory;
}

template<typename Type>
void xdelete(Type* obj)
{
	obj->~Type();
	xrelease(obj);
}
더보기
#include "pch.h"
#include "Memory.h"
#include "MemoryPool.h"

Memory::Memory()
{
	int32 size = 0;
	int32 tableIndex = 0;

	for (size = 32; size <= 1024; size += 32)
	{
		MemoryPool* pool = new MemoryPool(size);
		_pools.push_back(pool);

		while (tableIndex <= size)
		{
			_poolTable[tableIndex] = pool;
			tableIndex++;
		}
	}

	for (; size <= 2048; size += 128)
	{
		MemoryPool* pool = new MemoryPool(size);
		_pools.push_back(pool);

		while (tableIndex <= size)
		{
			_poolTable[tableIndex] = pool;
			tableIndex++;
		}
	}

	for (; size <= 4096; size += 256)
	{
		MemoryPool* pool = new MemoryPool(size);
		_pools.push_back(pool);

		while (tableIndex <= size)
		{
			_poolTable[tableIndex] = pool;
			tableIndex++;
		}
	}
}

Memory::~Memory()
{
	for (MemoryPool* pool : _pools)
		delete pool;

	_pools.clear();
}

void* Memory::Allocate(int32 size)
{
	MemoryHeader* header = nullptr;
	const int32 allocSize = size + sizeof(MemoryHeader);

	if (allocSize > MAX_ALLOC_SIZE)
	{
		// 메모리 풀링 최대 크기를 벗어나면 일반 할당
		header = reinterpret_cast<MemoryHeader*>(::malloc(allocSize));
	}
	else
	{
		// 메모리 풀에서 꺼내온다
		header = _poolTable[allocSize]->Pop();
	}

	return MemoryHeader::AttachHeader(header, allocSize);
}

void Memory::Release(void* ptr)
{
	MemoryHeader* header = MemoryHeader::DetachHeader(ptr);

	const int32 allocSize = header->allocSize;
	ASSERT_CRASH(allocSize > 0);

	if (allocSize > MAX_ALLOC_SIZE)
	{
		// 메모리 풀링 최대 크기를 벗어나면 일반 해제
		::free(header);
	}
	else
	{
		// 메모리 풀에 반납한다
		_poolTable[allocSize]->Push(header);
	}
}
extern class Memory*			GMemory;

class PoolAllocator
{
public:
	static void*	Alloc(int32 size);
	static void		Release(void* ptr);
};

void* PoolAllocator::Alloc(int32 size)
{
	return GMemory->Allocate(size);
}

void PoolAllocator::Release(void* ptr)
{
	GMemory->Release(ptr);
}

메모리 같은 경우 1024 까지는 32, 2048까지는 128단위, 4096까지는 256byte 단위로 MemoryPool을 만듭니다.

예를 들어 1024 전 까지는 32 64 96 ~~~ 1024byte의 크기의 MemoryPool을 만들게 됩니다.

_poolTable은 크기에 맞는 Table을 HashMap으로 쓰기 위해 존재합니다.

 

이 코드는 Lock을 통해서 MemoryPool에 동시에 접근하는 스레드들을 컨트롤하게 됩니다.

'프로그래밍 언어 > C++' 카테고리의 다른 글

STLAllocator  (0) 2023.04.02
VirtualAlloc - PAGE 크기로 동적 할당  (0) 2023.04.02
placement new문법(객체 초기화)  (0) 2023.03.27
shared_ptr 구현기 - 1일차  (0) 2023.03.27
C++) STL 목차 정리  (0) 2022.11.24
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함