KoreanFoodie's Study
[C++ 게임 서버] 2-9. Object Pool 본문
[C++ 게임 서버] 2-9. Object Pool
핵심 :
1. ObjectPool 을 사용하면, 각 타입별로 풀을 만들게 되어, 메모리 오염 이슈가 발생했을 때 문제의 원인을 파악하기가 쉬워진다.
2. 커스텀한 삭제자를 객체 메모리를 풀에 반납하는 형식을 넣은 MakeShared 를 활용하면, ObjectPool 을 사용하는 스마트 포인터를 쉽게 만들 수 있다.
3. Allocator 를 StompAllocator 로 쉽게 교체할 수 있도록 #define 문법을 사용하면, 테스트용으로 StompAllocator 를 쉽게 적용 가능하다.
우리는 이전까지 메모리 풀에 대해 알아보았는데, 메모리 풀은 메모리 크기에 따라 객체들이 알아서 풀을 같이 사용한다.
집단 생활이 으레 그렇듯, 기숙사 생활에서도 문제가 생기는데 메모리 풀에서도 문제가 생기지 않을리가 없다 😂 특히 메모리풀을 이용했는데 메모리 오염이 발생하게 되면, 어떤 객체에서 문제가 발생했는지 쉽게 파악하기가 어려울 때가 있다.
그래서 객체의 타입별로 풀을 만드는 것이 좋다. 그리하여, 오늘 다룰 내용은 ObjectPool 되시겠다. 코드를 보자.
template<typename Type>
class ObjectPool
{
public:
template<typename... Args>
static Type* Pop(Args&&... args)
{
Type* memory = static_cast<Type*>(MemoryHeader::AttachHeader(s_pool.Pop(), s_allocSize));
new(memory)Type(forward<Args>(args)...); // placement new
return memory;
}
static void Push(Type* obj)
{
obj->~Type();
s_pool.Push(MemoryHeader::DetachHeader(obj));
}
static shared_ptr<Type> MakeShared()
{
shared_ptr<Type> ptr = { Pop(), Push };
return ptr;
}
private:
static int32 s_allocSize;
static MemoryPool s_pool;
};
template<typename Type>
int32 ObjectPool<Type>::s_allocSize = sizeof(Type) + sizeof(MemoryHeader);
template<typename Type>
MemoryPool ObjectPool<Type>::s_pool{ s_allocSize };
일단, 이제 오브젝트 풀 별로 타입이 각각 다를 것이다. 멤버 변수에 s_allocSize 와 s_pool 이 있는데, 각각 타입의 사이즈와 사용할 메모리 풀을 의미한다. 이 두 녀석은, 아래 부분에서 템플릿 클래스들이 생성(Generate) 될 때, 초기화 될 것이다.
이제 함수를 보자.
Pop 을 보면, 풀에 사용 가능한 녀석이 있으면 꺼내서 쓰고, 아니면 메모리를 새로 할당할 것임을 알 수 있다. 사실 Push 의 경우도 기존에 메모리 풀을 사용했던 방식과 비슷하다.
AttachHeader 와 DetachHeader 가 무엇이었는지 기억이 가물가물할 수 있는데, 사실 그냥 Header 의 위치를 타입의 포인터 사이즈 만큼 증가시키거나 감소시키면서, 메모리를 읽는 게 다다 😉
DECLSPEC_ALIGN(SLIST_ALIGNMENT)
struct MemoryHeader : public SLIST_ENTRY
{
// [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;
// TODO : 필요한 추가 정보
};
마지막으로, MakeShared 를 보자.
static shared_ptr<Type> MakeShared()
{
shared_ptr<Type> ptr = { Pop(), Push };
return ptr;
}
이 녀석은 왜 필요할까?
우리가 만든 ObjectPool 을 이용한 스마트 포인터를 만들때, c++ 에서 정의된 make_shared 를 사용하면 커스텀 삭제자를 사용할 수 없는 문제가 있다.
그래서 커스텀 삭제자에 Push 로직을 넣기 위해, make_shared 느낌으로 간편하게 사용할 Wrapper 함수를 만든 것이다 😁
이제 위의 오브젝트 풀은 다음과 같이 사용할 수 있다 :
class Knight
{
public:
int32 _hp = rand() % 1000;
};
int main()
{
Knight* knights[100];
// 생성
for (int32 i = 0; i < 100; i++)
knights[i] = ObjectPool<Knight>::Pop();
// 반납
for (int32 i = 0; i < 100; i++)
{
ObjectPool<Knight>::Push(knights[i]);
knights[i] = nullptr;
}
shared_ptr<Knight> sptr = ObjectPool<Knight>::MakeShared();
}
보면, Knight 객체를 오브젝트 풀 방식으로 100개 만들고, 다시 반납을 하고 있다.
ObjectPool 의 MakeShared 함수를 이용하면, 명시적인 delete 가 필요없는 스마트 포인터도 간단하게 만들 수 있다.
참고로, 다음과 같이 PoolAllocator 대신 StompAllocator 를 사용하여 Push 와 Pop 을 진행할 수도 있다 :
#ifdef _STOMP
MemoryHeader* ptr = reinterpret_cast<MemoryHeader*>(StompAllocator::Alloc(s_allocSize));
Type* memory = static_cast<Type*>(MemoryHeader::AttachHeader(ptr, s_allocSize));
#else
Type* memory = static_cast<Type*>(MemoryHeader::AttachHeader(s_pool.Pop(), s_allocSize));
#endif
new(memory)Type(forward<Args>(args)...); // placement new
return memory;
}
static void Push(Type* obj)
{
obj->~Type();
#ifdef _STOMP
StompAllocator::Release(MemoryHeader::DetachHeader(obj));
#else
s_pool.Push(MemoryHeader::DetachHeader(obj));
#endif
}
#define 만 잘 해주면, 테스트용으로 StompAllocator 를 사용할 수 있게 될 것이다 😎
마지막으로, 다음과 같이 MemoryPool 에 대한 공용 MakeShared 함수를 만들어 주면...
template<typename Type>
shared_ptr<Type> MakeShared()
{
return shared_ptr<Type>{ xnew<Type>(), xdelete<Type> };
}
MemoryPool 에 대해서도, MakeShared 를 아래 버전처럼 사용할 수도 있을 것이다!
// ObjectPool 을 위한 MakeShared
shared_ptr<Knight> sptr = ObjectPool<Knight>::MakeShared();
// MemoryPool 을 위한 MakeShared
shared_ptr<Knight> sptr2 = MakeShared<Knight>();
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 3-1. 소켓 프로그래밍 기초 #1 (0) | 2023.09.19 |
---|---|
[C++ 게임 서버] 2-10. TypeCast (0) | 2023.09.19 |
[C++ 게임 서버] 2-8. 메모리 풀 #3 (0) | 2023.09.14 |
[C++ 게임 서버] 2-7. 메모리 풀 #2 (0) | 2023.09.13 |
[C++ 게임 서버] 2-6. 메모리 풀 #1 (0) | 2023.09.11 |