/--------------------MonkeyAndy 翻译,转载请注明出处------------------------/
http://blog.youkuaiyun.com/MonkeyAndy
/--------------------MonkeyAndy 翻译,转载请注明出处------------------------/
目录
- 前言
- 它的工作原理是什么?
- 例子
- 使用代码
- 兴趣点
- 运行效率测量
- 关于代码
前言
解决方案:你自己的内存池
- 很少(或没有)堆碎片
- 速度比正常的内存分配(释放)要快(eg.通过 malloc, new)
- 检查一个任意的指针是否在内存池中
- 为你的硬盘驱动器写一个“堆回收站”(对于后续调试非常有益)
- 一些“内存泄漏检测”:当你还没有释放所有以前分配的内存时,内存池将抛出一个断言
它的工作原理是什么?

关于的MemoryChunks一个词
SMemoryChunk 结构体指针(m_ptrFirstChunk, m_ptrLastChunk, and m_ptrCursorChunk)。这些块建立起一个内存块链表。每一个指向链表中的下一个块。当一个内存块向操作系统申请内存时,它将完全由SMemoryChunks管理。让我们仔细看看在这样一个块: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)
- 链表中指向下一个内存块中的指针(Next)
第一步:预分配内存
/******************
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
******************/
<CODE>bool</CODE> CMemoryPool::AllocateMemory(const std::size_t &sMemorySize)
{
std::size_t sBestMemBlockSize = CalculateBestMemoryBlockSize(sMemorySize) ;
// allocate from Operating System
TByte *ptrNewMemBlock = (TByte *) malloc(sBestMemBlockSize) ;
...
那么,内存池 如何管理数据?
第二步:分配的内存的分割

//(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()将会根据现有内存的总量计算需要管理的内存块的数量。在通过malloc分配完内存块后,
ptrNewChunks 指向一个SMemoryChunks的数组。请注意,数组中的块中目前只有垃圾数据,因为我们还没有分配任何有意义的数据块成员。内存池 “堆” 看起来像这样:

SMemoryChunk allocation//(AllocateMemory() continued) :
...
// Associate the allocated Memory-Block with the Linked-List of MemoryChunks
return LinkChunksToData(ptrNewChunks, uiNeededChunks, ptrNewMemBlock) ;
/******************
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) ;
}
让我们一步一步的看这个重要的方法:第一线检查,如果已经有有效的块在列表中:
...
m_ptrFirstChunk = SetChunkDefaults(&(ptrNewChunks[0])) ;
m_ptrLastChunk = m_ptrFirstChunk ;
m_ptrCursorChunk = m_ptrFirstChunk;
...
uiMemOffSet = (i * ((unsigned int) m_sMemoryChunkSize)) ;
m_ptrLastChunk->Data = &(ptrNewMemBlock[uiMemOffSet]) ;
...
m_ptrLastChunk->Next = ptrNewChunk ;
m_ptrLastChunk = ptrNewChunk ;
...
在随后的 for 循环中,内存池将为
数组中的所有块
陆续分配到有效的数据。

Memory and chunks linked together, pointing to valid data
/******************
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 ;
}
DataSize
成员大于(或等于)请求的内存总量并且UsedSize 成员为0时,数据块能够容纳下请求的内存,最后,内存分割完成。为了使问题更加具体,我们假设内存池含有 600 字节 ,每块 100字节。

第三步:从内存池请求内存
/******************
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) ;
<CODE>if</CODE>(!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->
- 块的
DataSize必须大于或等于请求的内存总量 - 块的 UsedSize 必须为0
FindChunkSuitableToHoldMemory 方法完成。如果其返回 NULL, 则在内存池中没有可用的内存。这将导致AllocateMemory 调用(如上所述),它将从操作系统请求更多的内存。如果返回值不为NULL,一个有效的内存块被发现。 SetMemoryChunkValues 将调整该内存块的内部值,最后Data 指针返回给用户...
/******************
SetMemoryChunkValues
******************/
void CMemoryPool::SetMemoryChunkValues(SMemoryChunk *ptrChunk,
const std::size_t &sMemBlockSize)
{
if(ptrChunk)
{
ptrChunk->UsedSize = sMemBlockSize ;
}
...
}
例子

GetMemory 将会返回一个指向第一内存块的Data 指针,并且设置它的UsedSize 成员为300字节,因为300字节是内存块可以管理的最小内存量并且>= 250字节。剩余的 (300 - 250 = 50)字节被称为所谓的内存池的 “内存开销”(除了块本身所需的内存),这并不是很糟,因为它仍可使用,(它仍然是在内存池中)。当FindChunkSuitableToHoldMemory 搜索有效的内存块时,它会从一个“空块”跳到另一个“空块”搜索。这意味着,如果有人请求另一个内存块,例子中的第四块(例如在一个300字节的内存池中)将成为下一个“有效”的块。
使用代码
- CMemoryPool.h
- CMemoryPool.cpp
- IMemoryBlock.h
- SMemoryChunk.h
使用例子
MemPool::CMemoryPool *g_ptrMemPool = new MemPool::CMemoryPool() ;
char *ptrCharArray = (char *) g_ptrMemPool->GetMemory(100) ;
...
g_ptrMemPool->FreeMemory(ptrCharArray, 100) ;
delete g_ptrMemPool ;
兴趣点
内存转存
/******************
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") ;
看下内存转存文件 memory dump-file (" MemoryDump.bin "):

m_iMyInt (3930 0000 = 0x3039 = 12345 decimal) 等都出现在这里。这非常有利于事后研究。运行效率测量

//Class-Test for MemoryPool and Heap (overloaded new/delete)
for(unsigned int j = 0; j < TestCount; j++)
{
MyTestClass *ptrTestClass = new MyTestClass ;
delete ptrTestClass ;
}
new/delete operators)
About the code...
The code has been tested under MS Windows and Linux with the following C++ compiler(s):
- Microsoft Visual C++ 6.0
- Microsoft Visual C++ .NET 2003
- MinGW (GCC) 3.4.4 (Windows)
- GCC 4.0.X (Debian GNU Linux)
Project files for Microsoft Visual C++ 6.0 (*.dsw, *.dsp) and Microsoft Visual C++ .NET 2003 (*.sln, *.vcproj) are included in the download. This memory pool uses only ANSI/ISO C++, so it should compile on any OS with a decent C++ compiler. There should be no problem using it on a 64-bit processor.
Note: The memory pool is not thread-safe!
ToDo
This memory pool is far from being perfect ;-) The ToDo-list includes:
- For huge amounts of memory, the memory-"overhead" can be quite large.
- Some
CalculateNeededChunkscalls can be stripped off by redesigning some methods => Even more speed up. ;-) - More stability tests (especially for very long-running applications).
- Make it thread-safe.
Histroy
- 05.09.2006: Initial release.
EoF
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)
About the Author

本文深入探讨了C++中内存池的工作原理、实现细节及应用案例,旨在提升应用程序性能,减少内存碎片并加速内存分配与释放过程。通过详细解释内存池的架构与操作流程,包括内存预分配、内存分割、内存请求与释放机制,以及如何通过内存池管理内存块,文章还展示了内存池在实际应用中的高效表现与优势。
Germany
1365

被折叠的 条评论
为什么被折叠?



