본문 바로가기

[단계별로 IOCP 실습] 2단계 메모리 최적화

@iamrain2025. 8. 1. 15:29

2단계에서는 아래의 내용을 진행한다.

  • `OverlappedEx` 구조체에서 버퍼 관리 최적화
  • 불필요한 메모리 낭비 제거
  • 코드 구조 개선

코드 분리

이전 코드에서 `IOCompletionPort.h`에 `stOverlappedEx`와 `stClientInfo` 구조체가 있었다.

하나의 파일에 모든 코드가 들어가 있으면 구조적으로 좋지 않기 때문에 분리하는 작업을 진행한다.

 

Server 코드는 그대로 두고, enum class와 구조체를 정의한 코드만 분리하여 별도의 파일로 만들었다.

`Define.h`

#pragma once

#include <winsock2.h>
#include <Ws2tcpip.h>

const UINT32 MAX_SOCKBUF = 256;
const UINT32 MAX_WORKERTHREAD = 4;

enum class IOOperation
{
	RECV,
	SEND
};

struct stOverlappedEx
{
	WSAOVERLAPPED m_wsaOverlapped;
	SOCKET		m_socketClient;
	WSABUF		m_wsaBuf;
	char		m_szBuf[ MAX_SOCKBUF ];
	IOOperation m_eOperation;
};	
	
struct stClientInfo
{
	SOCKET			m_socketClient;
	stOverlappedEx	m_stRecvOverlappedEx;
	stOverlappedEx	m_stSendOverlappedEx;
	
	stClientInfo()
	{
		ZeroMemory( &m_stRecvOverlappedEx , sizeof( stOverlappedEx ) );
		ZeroMemory( &m_stSendOverlappedEx , sizeof( stOverlappedEx ) );
		m_socketClient = INVALID_SOCKET;
	}
};

 

이렇게 분리된 코드를 기존 Server에서 사용해야 하기 때문에 `include` 해준다.

#pragma once
#pragma comment(lib, "ws2_32")

#include "Define.h"
#include <thread>
#include <vector>

class IOCompletionPort
{
	...

 

`OverlappedEx` 구조체에서 버퍼 관리 최적화

기존 `stOverlappedEx` 구조체에서는 `char m_szBuf[MAX_SOCKBUF]`로 Receive와 Send를 하나로 사용하고 있다. 버퍼 데이터를 저장하는 위치를 옮기고 Receive와 Send 버퍼를 분리했다.

#pragma once

#include <winsock2.h>
#include <Ws2tcpip.h>

const UINT32 MAX_SOCKBUF = 256;
const UINT32 MAX_WORKERTHREAD = 4;

enum class IOOperation
{
	RECV,
	SEND
};

struct stOverlappedEx
{
	WSAOVERLAPPED m_wsaOverlapped;
	SOCKET		m_socketClient;
	WSABUF		m_wsaBuf;
	IOOperation m_eOperation;
};

struct stClientInfo
{
	SOCKET			m_socketClient;
	stOverlappedEx	m_stRecvOverlappedEx;
	stOverlappedEx	m_stSendOverlappedEx;
	
	char			mRecvBuf[MAX_SOCKBUF]; //데이터 버퍼
	char			mSendBuf[MAX_SOCKBUF]; //데이터 버퍼

	stClientInfo()
	{
		ZeroMemory(&m_stRecvOverlappedEx, sizeof(stOverlappedEx));
		ZeroMemory(&m_stSendOverlappedEx, sizeof(stOverlappedEx));
		m_socketClient = INVALID_SOCKET;
	}
};

 

구조체가 변경되면서 기존 코드에서 에러가 발생한다. 적절하게 수정하자.

 

메모리 낭비? 어디서?

기존 구조체에서 어떤 부분이 메모리를 낭비하는 걸까?

struct stOverlappedEx
{
	WSAOVERLAPPED m_wsaOverlapped;     // 32바이트 (Windows x64 기준)
	SOCKET        m_socketClient;      // 8바이트
	WSABUF        m_wsaBuf;            // 16바이트 (포인터 + int)
	char          m_szBuf[MAX_SOCKBUF];// 256바이트
	IOOperation   m_eOperation;        // 4바이트 (enum class, 기본 int형)
};

 

크기 (대략) = 32 + 8 + 16 + 256 + 4 = 316바이트 (패딩 포함하면 약 320바이트 이상 될 수 있음)
struct stClientInfo
{
	SOCKET         m_socketClient;     // 8바이트
	stOverlappedEx m_stRecvOverlappedEx; // 320바이트
	stOverlappedEx m_stSendOverlappedEx; // 320바이트
};
총 크기 ≈ 8 + 320 + 320 = 648바이트 per client

모든 클라이언트 당 송수신 버퍼가 256바이트씩 중복으로 존재한다.

`m_szBuf`는 `WSABUF.buf`로 연결되기만 하고, 결국 `stClientInfo` 전체에서 `RecvBuf`와 `SendBuf`가 존재하게 되어 실제 사용되는 버퍼와 구조체 내 버퍼가 이중화될 수 있다.

비교 테스트

int main()
{
    cout << "========== 구조체 메모리 비교 ==========\n";
    cout << "Overlapped 객체 수: " << OVERLAPPED_COUNT << "\n\n";

    cout << "[With Buffer 구조체]\n";
    cout << "OverlappedWithBuffer 크기  : " << sizeof(OverlappedWithBuffer) << " bytes\n";
    cout << "총 메모리 사용량           : "
        << (sizeof(OverlappedWithBuffer) * OVERLAPPED_COUNT) / 1024 << " KB ("
        << (sizeof(OverlappedWithBuffer) * OVERLAPPED_COUNT) << " bytes)\n\n";

    cout << "[No Buffer 구조체]\n";
    cout << "OverlappedNoBuffer 크기     : " << sizeof(OverlappedNoBuffer) << " bytes\n";
    cout << "총 메모리 사용량            : "
        << (sizeof(OverlappedNoBuffer) * OVERLAPPED_COUNT) / 1024 << " KB ("
        << (sizeof(OverlappedNoBuffer) * OVERLAPPED_COUNT) << " bytes)\n\n";

    size_t savedPerClient = sizeof(OverlappedWithBuffer) - sizeof(OverlappedNoBuffer);

    cout << "========== 절감 효과 ==========\n";
    cout << "객체 1개당 절약 메모리 : " << savedPerClient << " bytes\n";
    cout << "전체 절약된 메모리     : " << savedPerClient * OVERLAPPED_COUNT / 1024 << " KB\n";

    return 0;
}

 

iamrain
@iamrain :: Annals of Unreal

iamrain 님의 블로그 입니다.

공감하셨다면 ❤️ 구독도 환영합니다! 🤗

목차