상세 컨텐츠

본문 제목

Memory pool #3

똑똑한 개발/C++ 게임개발

by 성댕쓰 2021. 9. 30. 22:30

본문

마이크로 소프트에서 제공하는 lock free stack을 활용하여 #2 와 같은 기능을 하는 memory pool을 만들 수 있다.

먼저 간단한 활용을 살펴보자.

 

DECLSPEC_ALIGN(16)
class Data
{
public:
	SLIST_ENTRY _entry;
	int64 _rand = rand() % 1000;
};

SLIST_HEADER* GHeader;

int main()
{
	GHeader = new SLIST_HEADER();
	ASSERT_CRASH(((uint64)GHeader % 16) == 0);
	::InitializeSListHead(GHeader);

	for (int32 i = 0; i < 3; i++)
	{
		GThreadManager->Launch([]()
			{
				while (true)
				{
					Data* data = new Data();
					ASSERT_CRASH(((uint64)data % 16) == 0);

					::InterlockedPushEntrySList(GHeader, (SLIST_ENTRY*)data);
					this_thread::sleep_for(10ms);
				}
			});
	}

	for (int32 i = 0; i < 2; i++)
	{
		GThreadManager->Launch([]()
			{
				while (true)
				{
					Data* pop = nullptr;
					pop = (Data*)::InterlockedPopEntrySList(GHeader);

					if (pop)
					{
						cout << pop->_rand << endl;
						delete pop;
					}
					else
					{
						cout << "NONE" << endl;
					}
				}
			});
	}

	GThreadManager->Join();
}

SLIST_HEADER, SLIST_ENTRY를 이용하여 memory pool을 만들어 보자.

 

MemoryPool.h

#pragma once

enum
{
	SLIST_ALIGNMENT = 16
};

/*---------------------
	MemoryHeader
---------------------*/
DECLSPEC_ALIGN(SLIST_ALIGNMENT)
struct MemoryHeader : public SLIST_ENTRY
{
	// [MemoryHeader][Data] : 표준 new, delete도 같은 방식

	MemoryHeader(int32 size) : allocSize(size) {}

	static void* AttchHeader(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;
};

/*---------------------
	MemoryPool
---------------------*/

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

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

private:
	SLIST_HEADER _header;
	int32 _allocSize = 0;
	atomic<int32> _allocCount = 0;
};

MemoryPool.cpp

#include "pch.h"
#include "MemoryPool.h"

/*---------------------
    MemoryPool
---------------------*/

MemoryPool::MemoryPool(int32 allocSize) : _allocSize(allocSize)
{
    ::InitializeSListHead(&_header);
}

MemoryPool::~MemoryPool()
{
    while (MemoryHeader* memory = static_cast<MemoryHeader*>(::InterlockedPopEntrySList(&_header)))
    {
        ::_aligned_free(memory);
    }
}

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

    // Pool에 메모리 반납
    ::InterlockedPushEntrySList(&_header, static_cast<PSLIST_ENTRY>(ptr));

    _allocCount.fetch_sub(1);
}

MemoryHeader* MemoryPool::Pop()
{
    MemoryHeader* memory = static_cast<MemoryHeader*>(::InterlockedPopEntrySList(&_header));

    // 없으면 새로 만든다
    if (memory == nullptr)
    {
        memory = reinterpret_cast<MemoryHeader*>(::_aligned_malloc(_allocSize, SLIST_ALIGNMENT));
    }
    else
    {
        ASSERT_CRASH(memory->allocSize == 0);
    }

    _allocCount.fetch_add(1);

    return memory;
}

Memory.cpp

...
void* Memory::Allocate(int32 size)
{
	MemoryHeader* header = nullptr;

	const int32 allocSize = size + sizeof(MemoryHeader);

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

    return MemoryHeader::AttchHeader(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)
	{
		// 메모리 풀링 최대 크기를 벗어나면 일반 해제
		::_aligned_free(header);
	}
	else
	{
		// 메모리 풀에 반납한다.
		_poolTable[allocSize]->Push(header);
	}
}

 

아래처럼 사용할 수 있다.

class Knight
{
public:
	int32 _hp = rand() % 1000;
};

int main()
{
	for (int32 i = 0; i < 5; i++)
	{
		GThreadManager->Launch([]()
			{
				while (true)
				{
					Knight* knight = xnew<Knight>();
					cout << knight->_hp << endl;

					this_thread::sleep_for(10ms);

					xdelete(knight);
				}
			});
	}

	GThreadManager->Join();
}

1. SLIST_ENTRY를 모든 힙 할당에 사용하는 방법은?

- SLIST_ENTRY를 상속하여 MemoryHeader를 정의한다.

- LockFree stack을 사용하기 위해, MemoryPool에 SLIST_HEADER를 멤버변수로 선언한다.

2. _alined_malloc을 사용하는 이유는?

- malloc은 aligned 16 byte를 보장하지 않음.

참조 : [C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버 - 인프런 | 강의 (inflearn.com)

'똑똑한 개발 > C++ 게임개발' 카테고리의 다른 글

TypeCast  (0) 2021.10.29
Object pool  (0) 2021.10.01
Memory pool #2  (0) 2021.09.23
Memory pool  (0) 2021.09.15
STL allocator  (0) 2021.09.12

관련글 더보기

댓글 영역