고수닷넷 - bro님
[소개]
server side 프로그래밍시 같은 크기의 객체를 new / delete를 자주 해야 할경우 VMemPool은 기본 힙 연산 수행보다 빠른 성능을 낼수 있습니다.
new/delete 연산은 아시다 싶이 많은 cpu 시간을 요구하게 됩니다. 또한 서버 같이 오랫동안 실행되는 프로그램은 잦은 new/delete 호출로 메모리 단편화가 생겨 초기의 new/delete 호출보다도 계속 느리게 처리될 수도 있습니다.
만일 서버에서 같은 크기의 객체를 new /delete 해야할 일이 생긴다면 이런 상황에서 VMemPool은 메모리 관리에 좋은 성능을 보일 수 있습니다.
[구현]
VMemPool은 기존 new /delete 와 동일 한 방법으로 사용할 수 있도록 하며, 클래스 상속시 각 클래스별 static 맴버 변수를 할당 할 수 있도록 템플리트를 이용하여 구현하였습니다.
VMemPool 내부에는 각 객체가 블럭마다 할당되었는지 알아 보는 할당 관련 bitset이 존재하며, 아직 할당 되지 않은 free block에 대한 원형큐 배열로 구현되어 있습니다.
[사용하기]
일반적으로 c++ 에서 new/ delete 는 아래와 같이 사용 할 수 있습니다.
CObj* p = new CObj;
p->do();
delete p;
VMemPool을 이용하여 위와 같이 하기 위해서는 먼저 메모리 풀을 사용할 객체에 VMemPool을 상속 시키는 일입니다. 이때 템플리트 인자로 풀의 사이즈를 정할 수 있습니다. default template value를 두어 크기를 정하지 않으면 1000 으로 잡히게 됩니다.
// suppose CVMemPool is like below. it's not real code.
template <DWORD _dwPoolSizeT = 1000>
class CVMemPool
{
...
};
class CObj1 : public CVMemPool<>
{
...
};
class CObj2 : public CVMemPool<>
{
...
};
CObj1 c1;
CObj2 c2; // it will share pool with c1 , it is not good. cos i need objT.
이렇게 해주면 사용할때는 기존 new /delete 와 같습니다.
//make class in pool.
class CObj : public CVMemPool<CObj>
{
...
};
// and you can use it same like general new/delete code.
CObj* p = new CObj; // Pool is created, and allocation in first pool block.
CObj* p2 = new CObj; // second pool block will be used.
delete p; // first block will be freed.
delete p2; // second ,too.
[성능]
첨부 화일은 데모 프로젝트 입니다. VMemPool 소스가 함께 동봉되어 있습니다.
테스트 환경 :
P4 1.6GHz, 256MB ram, Windows 2000 Professional, release executable testing.
두가지 상황 :
첫째로, CObj 는 1,000 bytes size 이며 new/delete를 10,000번 , 20.000번 .... 합니다.
둘째는, CObj가 10,000 bytes size 이며 new/delete를 10,000번 , 20.000번 .... 합니다.
(그림상 n * 1,000 는 틀립니다. n* 10,000 이 맞습니다. )
결과는 아래와 같았습니다.
정리
VMemPool을 이용하면 동일한 크기의 객체에 대해 자주 할당/해제를 해야 할경우 기본 힙에 대한 new/delete 보다 연산 수행이 빠르다는 것을 알 수 있습니다. 서버 환경에서 메모리 관리는 무척 중요하다는 사실과 VMemPool은 그런 상황에서 보다 좋은 성능으로 사용될 수 있다는 점을 알게 되었습니다.
본 문서는 코드프로젝트에서도 찾아보실 수 있습니다.
http://www.codeproject.com/cpp/vmempool.asp
코드 프로젝트에서 또다른 효율적인 메모리 관리 소스 및 제품에 대한 소개가 답글로 달려 있어서 유용한 정보라 생각되어 정리해 놓습니다.
- SmartHeap ( http://www.microquill.com/smartheap/index.html )
- A Memory Allocator ( http://gee.cs.oswego.edu/dl/html/malloc.html )
소스의 주석은 첨엔 한글본으로 만들었다가 코드 프로젝트 등에 올리기 위해서 유지관리를 위해 편의상 영문으로 했습니다.
한글본 소스는 한글 사이트 이므로 소스 분석 이해에 도움이 될지 몰라 아래에 담습니다. 감사합니다.
VMemPool.h
// VMemPool.h: interface for the CVMemMan class.
//
// Virtual Memory Pool Management Class
// by bro (bro@jiran.com)
// 2002-02-19
//
//--------------------------------------------------------------------
// History.
// 2002-02-19 : Fisrt Code.
// 2002-02-20 : vmXXX public Function 지원
// DEBUG_NEW 지원
// 2002-03-08 : New mechanism : alloc bitset, not use std::list is so heavy.
// just use 'Free-MemoryBlock-Queue'
// Support Helper Function: IsBadPtr.
// 2002-07-31 : Bug FIx. No Criticalsection, now use event kernel object.
// i don't know why. but, error occurs when criticalsection
// unlock on exit in Win32 service code.
//--------------------------------------------------------------------
// Manual
// 사용방법
/*
class CObj : public CVMemPool<>
{
public:
:
};
CObj* pObj = new CObj; // Virtual Memory Pool 1000칸생김, 첫번째 불럭에 할당
CObj* pObj2 = new CObj;// 두번째 블럭에 할당
delete pObj; // 첫번째 블럭 반환
delete pObj2; // 두번째 블럭 반환
*//////////////////////////////////////////////////////////////////////
#if !defined(AFX_VMEMPOOL_H__BD52675B_6C82_4CDE_8618_0141D7A4653F__INCLUDED_)
#define AFX_VMEMPOOL_H__BD52675B_6C82_4CDE_8618_0141D7A4653F__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include "BitSet.h"
//====================================================================
// CVMemPool<int T> : Virtual Memory Pool Management Class
//--------------------------------------------------------------------
// 원하는 크기만큼의 풀을 생성하여 상속받은 클래스 객체들을 관리한다.
//====================================================================
template <class objT,DWORD _dwPoolSizeT = 1000>
class CVMemPool
{
public:
//===========================================================
// 함수명 : vmInitPool
// 설 명 : 풀 사이즈를 동적으로 설정하거나, 바로 메모리풀을
// 생성할수 있음 ( 객체 new하기전에 미리 호출해야함)
// 인 자 : nPoolSize - 풀의 크기 (들어갈수있는 객체총갯수)
// bAllocNow - 바로 Virtual 메모리를 초기생성한다.
// nObjSize - bAllocNow일때만 윻, 한 객체의 bytes 크기
// 리턴값 : bAllocNow일때 Virtual메모리 생성 실패시 FALSE리턴
//-----------------------------------------------------------
// 작성자 작성일 작성이유
// 조경민 2002-02-20
//===========================================================
static inline BOOL vmInitPool( DWORD dwPoolSize , BOOL bAllocNow = FALSE, DWORD dwObjSize = 0 )
{
ms_dwPoolSize = dwPoolSize;
if( bAllocNow && dwObjSize > 0 )
{
ms_dwObjSize = dwObjSize;
if( !AllocPool(ms_dwPoolSize,ms_dwObjSize) )
{
return FALSE;
}
}
return TRUE;
}
//===========================================================
// 함수명 : vmCleanUp
// 설 명 : 프로세스 마지막 종료시 넣어주면 괜찮은 함수
// (사실 안넣어줘도 프로세스 소유안의 Virtual메모리
// 할당 공간은 파기된다. )
// 인 자 :
// 리턴값 :
//-----------------------------------------------------------
// 작성자 작성일 작성이유
// 조경민 2002-02-20
//===========================================================
static inline void vmCleanUp()
{
if( ms_pMemPool )
VirtualFree( ms_pMemPool, DWORD(ms_dwPoolSize*ms_dwObjSize), MEM_RELEASE|MEM_DECOMMIT );
DeleteCriticalSection(&ms_csLock.cs);
}
//===========================================================
// 함수명 : vmGetPoolInfo
// 설 명 : 풀의 사이즈나 하나의 블럭 사이즈, 현재 할당된 객체 갯수
// 등의 정보를 리턴하는 함수
// 인 자 : pdwPoolSize - [out] 풀의 크기(bytes가 아닌 들어갈수있는 객체
// 총 크기
// pdwAllocObjCount - [out] 현재 할당된 객체 갯수
// pdwObjSize - [out] 객체 하나의 bytes 값
// 리턴값 : Virtual메모리가 초기생성안되면 TRUE
//-----------------------------------------------------------
// 작성자 작성일 작성이유
// 조경민 2002-02-20
//===========================================================
static inline BOOL vmGetPoolInfo( DWORD* pdwPoolSize, DWORD* pdwAllocObjCount, DWORD* pdwObjSize )
{
BOOL bRet = TRUE;
if( !ms_pMemPool )
bRet = FALSE;
if( pdwPoolSize ) *pdwPoolSize = ms_dwPoolSize;
if( pdwAllocObjCount ) *pdwAllocObjCount = ms_dwAllocObjCount;
if( pdwObjSize ) *pdwObjSize = ms_dwObjSize;
return bRet;
}
// 옳바르지 않는 포인터 인지 조사
static inline BOOL vmIsBadPtr( void* p )
{
// 기본 메모리 블럭 규칙을 따르는가?
if( p && p>=ms_pData && p<=ms_pData+(ms_dwPoolSize*ms_dwObjSize)&&
((char*)p-ms_pData)%ms_dwObjSize == 0 )
{
// 할당된 메모리 블럭인가?
DWORD id = Addr2Id(p);
return (ms_bsAllocSet.Get(id)==0);
}
return TRUE;
}
struct CSLock
{
CRITICAL_SECTION cs;
CSLock(){ InitializeCriticalSection(&cs); }
void Lock(){ EnterCriticalSection(&cs); }
void Unlock() { LeaveCriticalSection(&cs); }
};
private:
static CBitSet ms_bsAllocSet; // 메모리 블럭의 할당 비트마스크
static DWORD ms_dwSrtFree; // 프리블럭 원형 큐 시작번호
static DWORD ms_dwEndFree; // 프리블럭 원형 큐 끝번호
static BOOL ms_bEmptyFree; // 프리 원형 큐가 비었음을 의미
static BOOL ms_bFullFree; // 프리 원형 큐가 꽉찼음을 의미
static DWORD ms_dwFreeQueueSize;
static DWORD ms_dwTotalSize;
static CSLock ms_csLock;
static char* ms_pMemPool; // 할당받은 Virtual 메모리 Pool
static char* ms_pData; // 할당받은 Virtual 메모리 Pool
static DWORD ms_dwPoolSize; // 객체가 들어갈 수 있는 풀 크기
static DWORD ms_dwObjSize; // 한 메모리 블럭의 크기=객체크기
static DWORD ms_dwAllocObjCount; // 현재 잡혀 있는 블럭 갯수
// Pool 메모리 객체 블럭 시작 주소를 블럭 레코드 번호로 변환
static inline DWORD Addr2Id( void* p )
{
return DWORD((char*)p-ms_pData)/ms_dwObjSize;
}
// 블럭 레코드 번호를 Pool 메모리 객체 블럭 시작 주소로 변환
static inline void* Id2Addr( DWORD id )
{
return ms_pData+(ms_dwObjSize*id);
}
// 프리블럭번호를 프리원형큐에 넣는다.
static inline BOOL InsertFree( DWORD id )
{
if( (ms_dwEndFree+1) % ms_dwFreeQueueSize == ms_dwSrtFree )
{
// 꽈찼음 OverFlow
return FALSE;
}
// 새로운 프리id를 넣는다.
*((DWORD*)ms_pMemPool+ms_dwEndFree) = id;
ms_dwEndFree = ++ms_dwEndFree % ms_dwFreeQueueSize;
// 프리되었다고 셋한다.
ms_bsAllocSet.Set( id, 0 );
//TRACE("free-inserted %d <%d-%d %d-%d>\r\n",id, ms_dwSrtFree,ms_dwEndFree, ms_bEmptyFree, ms_bFullFree);
return TRUE;
}
// 프리원형큐에서 프리블럭번호를 꺼내온다.
static inline DWORD PopFree()
{
if( ms_dwSrtFree == ms_dwEndFree )
{
// 비어있다. Underflow
return 0xFFFFFFFF;
}
// 프리한 id를 얻어온다.
DWORD id = *((DWORD*)ms_pMemPool+ms_dwSrtFree);
// 할당되었다고 세팅한다.
ms_bsAllocSet.Set( id, 1 );
// SrtFree를 원형증가시킨다.
ms_dwSrtFree=(++ms_dwSrtFree)%ms_dwFreeQueueSize;
//TRACE("free-poped %d <%d-%d %d-%d>\r\n",id, ms_dwSrtFree,ms_dwEndFree, ms_bEmptyFree, ms_bFullFree);
return id;
}
static inline BOOL AllocPool(DWORD dwPoolSize, DWORD dwObjSize)
{
DWORD dwUsedBytes = ms_bsAllocSet.sCalcUsedBytes(dwPoolSize);
DWORD dwFreeQueue = (dwPoolSize+1)*sizeof(DWORD);
DWORD dwTotalSize = dwUsedBytes + dwFreeQueue + dwPoolSize*dwObjSize;
ms_pMemPool = (char*)VirtualAlloc( NULL, dwTotalSize,
MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN,
PAGE_READWRITE );
if( !ms_pMemPool)
return FALSE;
ms_dwTotalSize = dwTotalSize;
ms_dwFreeQueueSize = dwPoolSize+1;
ms_pData = ms_pMemPool+dwUsedBytes+dwFreeQueue;
// 할당 비트 초기화 한다.
if(!ms_bsAllocSet.Create( dwPoolSize, TRUE, TRUE, ms_pMemPool+dwFreeQueue,dwUsedBytes ) )
return FALSE;
// 큐를 꽉 채움
ms_dwSrtFree = 0;
ms_dwEndFree = 0;
ms_bEmptyFree = TRUE;
ms_bFullFree = FALSE;
//TRACE("init <%d-%d %d-%d>\r\n",ms_dwSrtFree,ms_dwEndFree, ms_bEmptyFree, ms_bFullFree);
for( DWORD i = 0 ; i < dwPoolSize; i++)
{
InsertFree(i);
}
return TRUE;
}
public:
// DEBUG_NEW support function ( 메모리 트래킹은 지원안한다. )
inline void* operator new(size_t nSize, LPCSTR lpszFileName, int nLine)
{
return operator new( nSize );
}
inline void operator delete(void* p, LPCSTR lpszFileName, int nLine)
{
operator delete( p );
}
// new operator 재정의
void* operator new( size_t size )
{
ASSERT( size > 0 );
// 새로 할당 받을 주소를 담는 변수
void* pNew = NULL;
ms_csLock.Lock();
//====================================================
// 메모리 풀이 없다면 생성한다.
if( !ms_pMemPool )
{
if( !ms_dwPoolSize ) ms_dwPoolSize = _dwPoolSizeT;
ms_dwObjSize = size;
if( !AllocPool(ms_dwPoolSize,ms_dwObjSize) )
{
ms_csLock.Unlock();
return NULL;
}
}
// 쓸수있는 블럭을 주소로 얻는다.
DWORD id = PopFree();
if( id == 0xFFFFFFFF )
{
ms_csLock.Unlock();
return NULL;
}
pNew = Id2Addr(id);
//====================================================
if( pNew )
ms_dwAllocObjCount++;
else
;//TRACE(" new 할당실패 \r\n");
ms_csLock.Unlock();
return pNew;
}
// 메모리 해제
void operator delete( void* p )
{
ASSERT( p &&
p>=ms_pData&&
p<=ms_pData+(ms_dwPoolSize*ms_dwObjSize)&&
((char*)p-ms_pData)%ms_dwObjSize == 0 );
if( !p ) return;
// 락한다.
ms_csLock.Lock();
InsertFree( Addr2Id(p) );
//====================================================
// delete되어 다시 쓸수있는 아이템 리스트에 추가한다.
ms_dwAllocObjCount--;
//TRACE(" delete [%d]블럭 0x%X\r\n", Addr2Id(p), p);
//====================================================
// 언락한다.
ms_csLock.Unlock();
}
};
// 정적 변수 선언
template <class T,DWORD F> CBitSet CVMemPool<T,F>::ms_bsAllocSet;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwSrtFree;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwEndFree;
template <class T,DWORD F> BOOL CVMemPool<T,F>::ms_bEmptyFree;
template <class T,DWORD F> BOOL CVMemPool<T,F>::ms_bFullFree;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwTotalSize;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwFreeQueueSize;
template <class T,DWORD F> char* CVMemPool<T,F>::ms_pData;
template <class T,DWORD F> CVMemPool<T,F>::CSLock CVMemPool<T,F>::ms_csLock;
template <class T,DWORD F> char* CVMemPool<T,F>::ms_pMemPool = NULL;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwObjSize;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwAllocObjCount;
template <class T,DWORD F> DWORD CVMemPool<T,F>::ms_dwPoolSize;
#endif // !defined(AFX_VMEMPool_H__BD52675B_6C82_4CDE_8618_0141D7A4653F__INCLUDED_)
BitSet.h
// BitSet.h: interface for the CBitSet class.
//
// BitSet Mamangment Class
// 2002-03-08
// by bro ( bro@jiran.com )
//
// History
// 2002-03-18 Set 함수 버그 픽스 0으로 set할때 버그있었음
// 2002-03-19 % 나머지 연산자 옵티마이즈
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_BITSET_H__9C854732_4ED9_4D5D_84AB_8AFD4A9AA915__INCLUDED_)
#define AFX_BITSET_H__9C854732_4ED9_4D5D_84AB_8AFD4A9AA915__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
class CBitSet
{
BOOL m_bAutoDelete; // 자체 메모리 관리시 삭제여부
char* m_pBitSet; // 비트 집합 메모리 시작 주소
DWORD m_dwUsedBytes; // 사용중인 메모리 바이트수
public:
CBitSet();
virtual ~CBitSet();
// 미리 할당받을 바이트 값을 계산한다.
inline static DWORD sCalcUsedBytes( DWORD dwBits, BOOL b32BitAlign = TRUE )
{
// dwBits를 8비트로 나누어 몇번째 바이트안에 속해 있는지 본다.
DWORD dwUsedBytes = dwBits>>3;
// 만일 8로 나눈 나머지가 있다면 그 다음 바이트까지 사용중인것이다.
if( dwBits%8 ) dwUsedBytes++;
//return (dwBits>>3) + ((dwBits%8) ?
// (b32BitAlign ? sizeof(DWORD):1) : 0);
// 4바이트 정렬이 필요한 경우라면 4바이트로 정렬시켜서 더 더해준다.
if( b32BitAlign )
{
//<TODO> 64 비트로 바뀌면 Critical해진다. 그때 잘 생각해보자.
//dwUsedBytes += (sizeof(DWORD) - dwUsedBytes % sizeof(DWORD));
dwUsedBytes += (4 - (dwUsedBytes&3) );
}
return dwUsedBytes;
}
BOOL Create( DWORD dwBits, BOOL bZeroInit, BOOL b32BitAlign = TRUE, char* pszSource = NULL, DWORD dwSourceLen = 0 );
const DWORD GetUsedBytes() const
{
return m_dwUsedBytes;
}
inline char Get( DWORD dwBit )
{
//return m_pBitSet[dwBit>>3] & 1<<dwBit%8;
return m_pBitSet[dwBit>>3] & 1<<(dwBit&7);
}
inline void Set( DWORD dwBit, char bit )
{
// 1로 비트 세팅이라면 해당 관련 바이트에서
//bit?m_pBitSet[dwBit>>3]|=1<<dwBit%8:
// m_pBitSet[dwBit>>3]&=~(1<<dwBit%8);
bit?m_pBitSet[dwBit>>3]|=1<<(dwBit&7):
m_pBitSet[dwBit>>3]&=~(1<<(dwBit&7));
}
inline char operator [] (int nBit)
{
return Get( nBit );
}
};
#endif // !defined(AFX_BITSET_H__9C854732_4ED9_4D5D_84AB_8AFD4A9AA915__INCLUDED_)
BitSet.cpp
// BitSet.cpp: implementation of the CBitSet class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "BitSet.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
//#define new DEBUG_NEW
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CBitSet::CBitSet()
{
m_pBitSet = NULL;
m_dwUsedBytes = 0;
m_bAutoDelete = FALSE;
}
CBitSet::~CBitSet()
{
if( m_bAutoDelete )
{
if( m_pBitSet )
delete [] m_pBitSet;
}
}
//===========================================================
// 함수명 : Create
// 설 명 : Bit-Set을 초기화 한다.
// 인 자 : dwBits - 관리할 비트의 갯수
// bZeroInit - 초기화시 관리 비트 모두 0으로 초기화 할것인지 여부
// b32BitAlign - 4바이트 정렬을 할지 결정
// pszSource - NULL이 아니면 주어진 Source주소 공간을 사용
// dwSourceLen - 주어진 Source주소 공간의 크기
// 리턴값 : 초기화 성공여부
//----------------------------------------------------------
// 작성자 작성일 작성이유
// 조경민 2002-03-08
//===========================================================
BOOL CBitSet::Create( DWORD dwBits , BOOL bZeroInit, BOOL b32BitAlign /*= TRUE*/, char* pszSource /*= NULL*/, DWORD dwSourceLen /*= 0 */)
{
// 사용하게될 바이트들의 수를 얻는다.
m_dwUsedBytes = sCalcUsedBytes(dwBits, b32BitAlign);
// 사용하게될 바이트메모리를 얻는다.
m_pBitSet = pszSource;
// 사용하려는 원본 소스 메모리 영역보다 큰 영역을 요구한다.
if( pszSource && m_dwUsedBytes > dwSourceLen )
return FALSE;
// 소스가 없다 자체적인 소스를 얻어온다.
if( !pszSource )
{
m_pBitSet = new char[ m_dwUsedBytes ];
// 메모리 할당에 실패했다.
if(! m_pBitSet )
return FALSE;
m_bAutoDelete = TRUE;
}
if( bZeroInit )
memset( m_pBitSet, 0, m_dwUsedBytes );
return TRUE;
}
- 프로그램 중복 실행 방지 (0)2007/01/03
- 자석 윈도우 만들기 (0)2007/01/02
- VMemPool (0)2006/12/29
- CPU 클럭 구하기 (0)2006/12/29
- FlashWindow 함수를 사용하여 내 프로그램의 존재... (0)2006/12/27

수안이의 컴퓨터 연구실





Leave your greetings.