分类:
2013-03-19 22:01:56
原文地址:C++ Memory Pool 作者:Arthursky
原文作者: .
原文链接:
C++ 内存池
l
l

malloc, new等)的方式快
CMemoryPool的一小部分,参看由Doxygen生成的文档以得到详细的类描述。SMemoryChunk (m_ptrFirstChunk, m_ptrLastChunk, and m_ptrCursorChunk)的指针。这些块(chunks)建立一个内存块(memory chunks)的链表。各自指向链表中的下一个块(chunk)。当从操作系统分配到一块内存时,它将完全的被SMemoryChunks管理。让我们近一点看看一个块(chunk)。
typedef struct SMemoryChunk
{
TByte *Data ; // The actual Data
std::size_t DataSize ; // Size of the "Data"-Block
std::size_t UsedSize ; // actual used Size
bool IsAllocationChunk ; // true, when this MemoryChunks
// Points to a "Data"-Block
// which can be deallocated via "free()"
SMemoryChunk *Next ; // Pointer to the Next MemoryChunk
// in the List (may be NULL)
} SmemoryChunk;
Data),DataSize),UsedSize),CmemoryPool的构造函数,内存池(Memory Pool)将从操作系统申请它的第一块(大的)内存块(memory-chunk)
/*Constructor
******************/
CMemoryPool::CMemoryPool(const std::size_t &sInitialMemoryPoolSize,
const std::size_t &sMemoryChunkSize,
const std::size_t &sMinimalMemorySizeToAllocate,
bool bSetMemoryData)
{
m_ptrFirstChunk = NULL ;
m_ptrLastChunk = NULL ;
m_ptrCursorChunk = NULL ;
m_sTotalMemoryPoolSize = 0 ;
m_sUsedMemoryPoolSize = 0 ;
m_sFreeMemoryPoolSize = 0 ;
m_sMemoryChunkSize = sMemoryChunkSize ;
m_uiMemoryChunkCount = 0 ;
m_uiObjectCount = 0 ;
m_bSetMemoryData = bSetMemoryData ;
m_sMinimalMemorySizeToAllocate = sMinimalMemorySizeToAllocate ;
// Allocate the Initial amount of Memory from the Operating-System...
AllocateMemory(sInitialMemoryPoolSize) ;
}
AllocateMemory最终完成了从操作系统申请内存。
/******************
AllocateMemory
******************/
bool CMemoryPool::AllocateMemory(const std::size_t &sMemorySize)
{
std::size_t sBestMemBlockSize = CalculateBestMemoryBlockSize(sMemorySize) ;
// allocate from Operating System
TByte *ptrNewMemBlock = (TByte *) malloc (sBestMemBlockSize) ;
...
SMemoryChunks管理所有数据。从OS申请完内存之后,我们的块(chunks)和实际的内存块(block)之间就不存在联系:
SmemoryChunk的数组来管理内存块:
// (AllocateMemory()continued) :
...
unsigned int uiNeededChunks = CalculateNeededChunks(sMemorySize) ;
// allocate Chunk-Array to Manage the Memory
SMemoryChunk *ptrNewChunks =
(SMemoryChunk *) malloc ((uiNeededChunks * sizeof(SMemoryChunk))) ;
assert(((ptrNewMemBlock) && (ptrNewChunks))
&& "Error : System ran out of Memory") ;
...
CalculateNeededChunks()负责计算为管理已经得到的内存需要的块(chunks)的数量。分配完块(chunks)之后(通过malloc),ptrNewChunks将指向一个SmemoryChunks的数组。注意,数组里的块(chunks)现在持有的是垃圾数据,因为我们还没有给chunk-members赋有用的数据。内存池的堆(Memory Pool-"Heap"):
SMemoryChunk allocationAllocateMemory()会照顾它。LinkChunksToData()最后将把数据块(data block)和chunks联系起来,并将为每个chunk-member赋一个可用的值。
// (AllocateMemory()continued) :
...
// Associate the allocated Memory-Block with the Linked-List of MemoryChunks
return LinkChunksToData(ptrNewChunks, uiNeededChunks, ptrNewMemBlock) ;
LinkChunksToData():
/******************
LinkChunksToData
******************/
bool CMemoryPool::LinkChunksToData(SMemoryChunk *ptrNewChunks,
unsigned int uiChunkCount, TByte *ptrNewMemBlock)
{
SMemoryChunk *ptrNewChunk = NULL ;
unsigned int uiMemOffSet = 0 ;
bool bAllocationChunkAssigned = false ;
for(unsigned int i = 0; i < uiChunkCount; i++)
{
if(!m_ptrFirstChunk)
{
m_ptrFirstChunk = SetChunkDefaults(&(ptrNewChunks[0])) ;
m_ptrLastChunk = m_ptrFirstChunk ;
m_ptrCursorChunk = m_ptrFirstChunk ;
}
else
{
ptrNewChunk = SetChunkDefaults(&(ptrNewChunks[i])) ;
m_ptrLastChunk->Next = ptrNewChunk ;
m_ptrLastChunk = ptrNewChunk ;
}
uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
m_ptrLastChunk->Data = &(ptrNewMemBlock[uiMemOffSet]) ;
// The first Chunk assigned to the new Memory-Block will be
// a "AllocationChunk". This means, this Chunks stores the
// "original" Pointer to the MemBlock and is responsible for
// "free()"ing the Memory later....
if(!bAllocationChunkAssigned)
{
m_ptrLastChunk->IsAllocationChunk = true ;
bAllocationChunkAssigned = true ;
}
}
return RecalcChunkMemorySize(m_ptrFirstChunk, m_uiMemoryChunkCount) ;
}
...
if(!m_ptrFirstChunk)
...
...
m_ptrFirstChunk = SetChunkDefaults(&(ptrNewChunks[0])) ;
m_ptrLastChunk = m_ptrFirstChunk ;
m_ptrCursorChunk = m_ptrFirstChunk ;
...
m_ptrFirstChunk现在指向块数组(chunks-array)的第一个块,每一个块严格的管理来自内存(memory block)的m_sMemoryChunkSize个字节。一个”偏移量”(offset)——这个值是可以计算的所以每个(chunk)能够指向内存块(memory block)的特定部分。
uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
m_ptrLastChunk->Data = &(ptrNewMemBlock[uiMemOffSet]) ; 
SmemoryChunk将被追加到链表的最后一个元素(并且它自己将成为最后一个元素):
...
m_ptrLastChunk->Next = ptrNewChunk ;
m_ptrLastChunk = ptrNewChunk ;
...

DataSize 成员。
/******************
RecalcChunkMemorySize
******************/
bool CMemoryPool::RecalcChunkMemorySize(SMemoryChunk *ptrChunk,
unsigned int uiChunkCount)
{
unsigned int uiMemOffSet = 0 ;
for(unsigned int i = 0; i < uiChunkCount; i++)
{
if(ptrChunk)
{
uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
ptrChunk->DataSize =
(((unsigned int) m_sTotalMemoryPoolSize) - uiMemOffSet) ;
ptrChunk = ptrChunk->Next ;
}
else
{
assert(false && "Error : ptrChunk == NULL") ;
return false ;
}
}
return true ;
}
RecalcChunkMemorySize之后,每个chunk都知道它指向的空闲内存的大小。所以,将很容易确定一个chunk是否能够持有一块特定大小的内存:当DataSize成员大于(或等于)已经申请的内存大小以及DataSize成员是0,于是chunk有能力持有一块内存。最后,内存分割完成了。为了不让事情太抽象,我们假定内存池(memory pool )包含600字节,每个chunk持有100字节。

GetMemory:
/******************
GetMemory
******************/
void *CMemoryPool::GetMemory(const std::size_t &sMemorySize)
{
std::size_t sBestMemBlockSize = CalculateBestMemoryBlockSize(sMemorySize) ;
SMemoryChunk *ptrChunk = NULL ;
while(!ptrChunk)
{
// Is a Chunks available to hold the requested amount of Memory ?
ptrChunk = FindChunkSuitableToHoldMemory(sBestMemBlockSize) ;
if (!ptrChunk)
{
// No chunk can be found
// => Memory-Pool is to small. We have to request
// more Memory from the Operating-System....
sBestMemBlockSize = MaxValue(sBestMemBlockSize,
CalculateBestMemoryBlockSize(m_sMinimalMemorySizeToAllocate)) ;
AllocateMemory(sBestMemBlockSize) ;
}
}
// Finally, a suitable Chunk was found.
// Adjust the Values of the internal "TotalSize"/"UsedSize" Members and
// the Values of the MemoryChunk itself.
m_sUsedMemoryPoolSize += sBestMemBlockSize ;
m_sFreeMemoryPoolSize -= sBestMemBlockSize ;
m_uiObjectCount++ ;
SetMemoryChunkValues(ptrChunk, sBestMemBlockSize) ;
// eventually, return the Pointer to the User
return ((void *) ptrChunk->Data) ;
}
DataSize必须大于或等于被申请的内存的大小; UsedSize 必须是0。FindChunkSuitableToHoldMemory 方法完成。如果它返回NULL,那么在内存池中没有可用的内存。这将导致AllocateMemory 的调用(上面讨论过),它将从OS申请更多的内存。如果返回值不是NULL,一个可用的chunk被发现。SetMemoryChunkValues会调整chunk成员的值,并且最后Data指针被返回给用户...
/******************
SetMemoryChunkValues
******************/
void CMemoryPool::SetMemoryChunkValues(SMemoryChunk *ptrChunk,
const std::size_t &sMemBlockSize)
{
if(ptrChunk)
{
ptrChunk->UsedSize = sMemBlockSize ;
}
...
} 

GetMemory 从第一个chunk返回 Data指针并把它的UsedSize设为300字节,因为300字节是能够被管理的内存的最小值并大于等于250。那些剩下的(300 - 250 = 50)字节被称为内存池的"memory overhead"。这没有看起来的那么坏,因为这些内存还可以使用(它仍然在内存池里)。FindChunkSuitableToHoldMemory搜索可用chunk时,它仅仅从一个空的chunk跳到另一个空的chunk。那意味着,如果某个人申请另一块内存(memory-chunk),第四块(持有300字节的那个)会成为下一个可用的("valid") chunk。
CmemoryPool类的实例,你就可以从它里面申请内存。所有的内存池的配置在CmemoryPool类的构造函数(使用可选的参数)里完成。看一看头文件("CMemoryPool.h")或Doxygen-doku。所有的文件都有详细的(Doxygen-)文档。
MemPool::CMemoryPool *g_ptrMemPool = new MemPool::CMemoryPool() ;
char *ptrCharArray = (char *) g_ptrMemPool->GetMemory(100) ;
...
g_ptrMemPool->FreeMemory(ptrCharArray, 100) ;
delete g_ptrMemPool ;
WriteMemoryDumpToFile(strFileName)写一个"memory dump"到你的HDD。看看一个简单的测试类的构造函数(使用内存池重载了new和delete运算符):
/******************
Constructor
******************/
MyTestClass::MyTestClass()
{
m_cMyArray[0] = 'H' ;
m_cMyArray[1] = 'e' ;
m_cMyArray[2] = 'l' ;
m_cMyArray[3] = 'l' ;
m_cMyArray[4] = 'o' ;
m_cMyArray[5] = NULL ;
m_strMyString = "This is a small Test-String" ;
m_iMyInt = 12345 ;
m_fFloatValue = 23456.7890f ;
m_fDoubleValue = 6789.012345 ;
Next = this ;
}
MyTestClass *ptrTestClass = new MyTestClass ;
g_ptrMemPool->WriteMemoryDumpToFile("MemoryDump.bin") ;

MyTestClass类的所有成员的值。明显的,"Hello"字符串(m_cMyArray)在那里,以及整型数m_iMyInt (3930 0000 = 0x3039 = 12345 decimal)等等。这对调式很有用。timeGetTime()),但是结果说明内存池大大提高了应用程序的速度。所有的测试在Microsoft Visual Studio .NET 2003的debug模式下(测试计算机: Intel Pentium IV Processor (32 bit), 1GB RAM, MS Windows XP Professional).
//Array-test (Memory Pool):
for(unsigned int j = 0; j < TestCount; j++)
{
// ArraySize = 1000
char *ptrArray = (char *) g_ptrMemPool->GetMemory(ArraySize) ;
g_ptrMemPool->FreeMemory(ptrArray, ArraySize) ;
}
//Array-test (Heap):
for(unsigned int j = 0; j < TestCount; j++)
{
// ArraySize = 1000
char *ptrArray = (char *) malloc(ArraySize) ;
free(ptrArray) ;
}

//Class-Test for MemoryPool and Heap (overloaded new/delete)
for(unsigned int j = 0; j < TestCount; j++)
{
MyTestClass *ptrTestClass = new MyTestClass ;
delete ptrTestClass ;
}

CalculateNeededChunks调用能够通过从新设计某些方法而去掉