상세 컨텐츠

본문 제목

SendBuffer Pooling

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

by 성댕쓰 2023. 4. 5. 22:54

본문

Sendbuffer를 Pooling 하여 사용하도록 바꿔보자

CoreTLS.h, CoreTLS.cpp

#pragma once
#include <stack>

extern thread_local uint32 LThreadId;
extern thread_local std::stack<int32> LLockStack;
extern thread_local SendBufferChunkRef LSendBufferChunk;
#include "pch.h"
#include "CoreTLS.h"

thread_local uint32 LThreadId = 0;
thread_local std::stack<int32> LLockStack;
thread_local SendBufferChunkRef LSendBufferChunk;

SendBuffer.h, SendBuffer.cpp

#pragma once

class SendBufferChunk;
/*-----------
  SendBuffer
-----------*/

class SendBuffer
{
public:
	SendBuffer(SendBufferChunkRef owner, BYTE* buffer, int32 allocSize);
	~SendBuffer();

	BYTE* Buffer() { return _buffer; }
	int32 WriteSize() { return _writeSize; }
	void Close(uint32 writeSize);

private:
	BYTE* _buffer;
	uint32 _allocSize = 0;
	uint32 _writeSize = 0;
	SendBufferChunkRef _owner;
};

/*---------------------
  SendBufferChunk
---------------------*/

class SendBufferChunk : public enable_shared_from_this<SendBufferChunk>
{
	enum
	{
		SEND_BUFFER_CHUNK_SIZE = 6000
	};

public:
	SendBufferChunk();
	~SendBufferChunk();

	void Reset();
	SendBufferRef Open(uint32 allocSize);
	void Close(uint32 writeSize);

	bool IsOpen() { return _open; }
	BYTE* Buffer() { return &_buffer[_usedSize]; }
	uint32 FreeSize() { return static_cast<uint32>(_buffer.size()) - _usedSize; }

private:
	Array<BYTE, SEND_BUFFER_CHUNK_SIZE> _buffer = {};
	bool _open = false;
	uint32 _usedSize = 0;
};

/*---------------------
  SendBufferManager
---------------------*/

class SendBufferManager
{
public:
	SendBufferRef Open(uint32 size);

private:
	SendBufferChunkRef Pop();
	void Push(SendBufferChunkRef buffer);

	static void PushGlobal(SendBufferChunk* buffer);

private:
	USE_LOCK;
	Vector<SendBufferChunkRef> _sendBufferChunks;
};
#include "pch.h"
#include "SendBuffer.h"

/*-----------
  SendBuffer
-----------*/

SendBuffer::SendBuffer(SendBufferChunkRef owner, BYTE* buffer, int32 allocSize)
	: _owner(owner), _buffer(buffer), _allocSize(allocSize)
{

}

SendBuffer::~SendBuffer()
{
}

void SendBuffer::Close(uint32 writeSize)
{
	ASSERT_CRASH(_allocSize >= writeSize);
	_writeSize = writeSize;
	_owner->Close(writeSize);
}

/*---------------------
  SendBufferChunk
---------------------*/

SendBufferChunk::SendBufferChunk()
{
}

SendBufferChunk::~SendBufferChunk()
{
}

void SendBufferChunk::Reset()
{
	_open = false;
	_usedSize = 0;
}

SendBufferRef SendBufferChunk::Open(uint32 allocSize)
{
	ASSERT_CRASH(allocSize <= SEND_BUFFER_CHUNK_SIZE);
	ASSERT_CRASH(_open == false);

	if (allocSize > FreeSize())
		return nullptr;

	_open = true;
	return ObjectPool<SendBuffer>::MakeShared(shared_from_this(), Buffer(), allocSize);
}

void SendBufferChunk::Close(uint32 writeSize)
{
	ASSERT_CRASH(_open == true);
	_open = false;
	// cursor 옮기기
	_usedSize += writeSize;
}

/*---------------------
  SendBufferManager
---------------------*/

//[                 ] 많이 예약해두고 일부만 사용
SendBufferRef SendBufferManager::Open(uint32 size)
{
	if (LSendBufferChunk == nullptr)
	{ 
		LSendBufferChunk = Pop(); // WRITE_LOCK
		LSendBufferChunk->Reset();
	} 

	ASSERT_CRASH(LSendBufferChunk->IsOpen() == false);
	
	// 다 썼으면 버리고 새것으로 교체
	if (LSendBufferChunk->FreeSize() < size)
	{
		LSendBufferChunk = Pop(); // WRITE_LOCK
		LSendBufferChunk->Reset();
	}

	cout << "Free : " << LSendBufferChunk->FreeSize() << endl;

	return LSendBufferChunk->Open(size);
}

SendBufferChunkRef SendBufferManager::Pop()
{
	{
		WRITE_LOCK;
		if (_sendBufferChunks.empty() == false)
		{
			SendBufferChunkRef sendBufferChunk = _sendBufferChunks.back();
			_sendBufferChunks.pop_back();
			return sendBufferChunk;
		}
	}

	return SendBufferChunkRef(xnew<SendBufferChunk>(), PushGlobal);
}

void SendBufferManager::Push(SendBufferChunkRef buffer)
{
	WRITE_LOCK;
	_sendBufferChunks.push_back(buffer);
}

void SendBufferManager::PushGlobal(SendBufferChunk* buffer)
{
	GSendBufferManager->Push(SendBufferChunkRef(buffer, PushGlobal));
}

 

개선할 점? 매번마다 OnRecv에서 MakeShared ?? 근데, 메모리 풀 사용하잖아? 그럼에도 메모리 양이 커지면 pooling 안하고 new, delete 방식임.

메모리 미리 많이 잡아서 sendbuffer 잡는 게 낭비

 

크게 할당 (SendBufferChunk ) 잘라먹기 가 포인트

 

SendBufferManager에서 더 이상 사용하지 않는 메모리 관리하는 방법은?

- 메모리 풀에 해당 메모리 반납

 

SendBufferChunkRef를 tls에 만드는 이유는?

- lock 사용을 최소화 하기 위함.

 

할당 한 Chunk 메모리를 모두 사용하여 새 거로 교체되면 이전 메모리는 해제 되지 않음을 어떻게 보장하는지?

- SendBuffer는 WSASend에서 Ref 카운팅 되고 있다.

 

 

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

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

Buffer Helpers  (0) 2023.04.19
Packet Session  (0) 2023.04.16
SendBuffer  (0) 2021.12.21
RecvBuffer  (0) 2021.12.20
네트워크 라이브러리 만들기(Session #3)  (0) 2021.12.13

관련글 더보기

댓글 영역