利用C/C++開發大型應用程序中,內存的管理與分配是一個需要認真考慮的部分。本文描述了內存池設計原理並給出內存池的實現代碼,代碼支持Windows和Linux,多線程安全。內存池設計過程中需要考慮好內存的分配與釋放問題,其實也就是空間和時間的矛盾。有的內存池設計得很巧妙,內存分配與需求相當,但是會浪費過多的時間去查找分配與釋放,這就得不償失;實際使用中,我們更多的是關心內存分配的速度,而不是內存的使用效率。基於此,本文按照如下思想設計實現內存池。主要包含三個結構:StiaticMemory, MemoryChunk和MemoryBlock,三者之間的關系如下圖所示:
1.內存的分配:
(1)如果分配大小超過1024,直接采用malloc分配,分配的時候多分配sizeof(size_t)字節,用於保存該塊的大小;
(2)否則根據分配大小,查找到容納該大小的最小size的MemoryChunk;
(3)查找MemoryChunk的鏈表指針pList,找到空閒的MemoryBlock返回;
(4)如果pList為NULL,臨時創建MemoryBlock返回;
(5)MemoryBlock頭部包含兩個成員,pChunk指向的所屬的MemoryChunk對象,size表明大小,其後才是給用戶使用的空間;
2.內存的釋放:
(1)根據釋放的指針,查找器size頭部,即減去sizeof(size_t)字節,判斷該塊的大小;
(2)如果大小超過1024,直接free;
(3)否則交給MemoryChunk處理,而塊的頭部保存了該指針,因此直接利用該指針就可以收回該內存。
注意的問題:
上述設計的內存池通過冗余的頭部來實現內存塊的分配與釋放,減少了內存池的操作時間,速度上要優於原始的malloc和free操作,同時減少了內存碎片的增加。但是該設計中沒有去驗證釋放的塊冗余頭部的正確性,因此故意釋放不屬於內存池中的塊或者修改頭部信息都會導致內存池操作失敗,當然這些可以由程序員來控制。此外,內存池中分配出去的內存塊如果不主動釋放,內存池沒有保留信息,不會自動釋放,但是在退出的時候會驗證驗證是否完全釋放,其實這個在系統測試時候就可以檢測出來,我想這個缺陷也是可以彌補的,在此提出,希望使用者注意。
MemoryChunk.h 文件,線程安全
#ifndef MEMORY_CHUNK_H
#define MEMORY_CHUNK_H
#include <cstdio>
#include <cassert>
#include <cstdlib>
#ifdef WIN32
#include <windows.h>
typedef CRITICAL_SECTION MUTEXTYPE;
#define INITMUTEX(hMutex) InitializeCriticalSection(&hMutex)
#define DELMUTEX(hMutex) DeleteCriticalSection(&hMutex)
#define LOCK(hMutex) EnterCriticalSection(&hMutex)
#define UNLOCK(hMutex) LeaveCriticalSection(&hMutex)
#else
#include <pthread.h>
typedef pthread_mutex_t MUTEXTYPE;
#define INITMUTEX(hMutex) pthread_mutex_init(&hMutex,NULL)
#define DELMUTEX(hMutex) pthread_mutex_destroy(&hMutex)
#define LOCK(hMutex) pthread_mutex_lock(&hMutex)
#define UNLOCK(hMutex) pthread_mutex_unlock(&hMutex)
#endif
class MemoryChunk;
/** @struct MemoryBlock
*
*/
struct BlockHeader
{
MemoryChunk* pChunk;
size_t len;
};
struct MemoryBlock;
struct BlockData
{
union{
MemoryBlock* pNext;
char pBuffer;
};
};
struct MemoryBlock
{
BlockHeader header;
BlockData data;
};
/** @class MemoryChunk
*
*/
class MemoryChunk
{
public:
MemoryChunk(size_t size, int count)
{
INITMUTEX(hMutex);
this->pFreeList=NULL;
this->size=size;
this->count=0;
MemoryBlock* pBlock;
while(count--){
pBlock=CreateBlock();
if(!pBlock)break;
pBlock->data.pNext=pFreeList;
pFreeList=pBlock;
}
}
~MemoryChunk()
{
int tempcount=0;
MemoryBlock* pBlock;
while(pBlock=pFreeList){
pFreeList=pBlock->data.pNext;
DeleteBlock(pBlock);
++tempcount;
}
assert(tempcount==count);//!確保釋放完全
DELMUTEX(hMutex);
}
void* malloc()
{
MemoryBlock* pBlock;
LOCK(hMutex);
if(pFreeList){
pBlock=pFreeList;
pFreeList=pBlock->data.pNext;
}
else{
if(!(pBlock=CreateBlock())){
UNLOCK(hMutex);
return NULL;
}
}
UNLOCK(hMutex);
return &pBlock->data.pBuffer;
}
static void free(void* pMem)
{
MemoryBlock* pBlock=(MemoryBlock*)((char*)pMem-sizeof(BlockHeader));
pBlock->header.pChunk->free(pBlock);
}
void free(MemoryBlock* pBlock)
{
LOCK(hMutex);
pBlock->data.pNext=pFreeList;
pFreeList=pBlock;
UNLOCK(hMutex);
}
MemoryChunk* Next(){return pNext;}
protected:
MemoryBlock* CreateBlock()
{
MemoryBlock* pBlock=(MemoryBlock*)::malloc(sizeof(BlockHeader)+size);
if(pBlock){
pBlock->header.pChunk=this;
pBlock->header.len=size;
++count;
}
return pBlock;
}
void DeleteBlock(MemoryBlock* pBlock)
{
::free(pBlock);
}
private:
MemoryBlock* pFreeList;
size_t size;//!Block大小
int count;//!Block數目
MemoryChunk* pNext;
MUTEXTYPE hMutex;
};
#endif
StaticMemory.h文件,內存池對象
#ifndef STATIC_MEMORY_H
#define STATIC_MEMORY_H
#include "MemoryChunk.h"
/** @ StaticMemory.h
* 定義實現內存池
* 采用固定大小策略進行內存管理與分配
* 減少因大量小內存分配導致的內存碎片增加
*/
struct HeapHeader
{
size_t size;
};
struct MemoryHeap
{
HeapHeader header;
char pBuffer;
};
class StaticMemory
{
public:
typedef enum{MAX_SIZE=1024,MIN_SIZE=sizeof(MemoryChunk*)};
StaticMemory()
{
chunkcount=0;
for(size_t size=MIN_SIZE; size<=MAX_SIZE; size*=2)++chunkcount;
//pChunkList=(MemoryChunk**)malloc(sizeof(MemoryChunk*)*chunkcount);
pChunkList=new MemoryChunk*[chunkcount];
int index=0;
for(size_t size=MIN_SIZE; size<=MAX_SIZE; size*=2)
{
pChunkList[index++]=new MemoryChunk(size,1000);
}
}
~StaticMemory()
{
for(int index=0; index<chunkcount; ++index)
{
delete pChunkList[index];
}
//free(pChunkList);
delete[] pChunkList;
}
void* Malloc(size_t size)
{
if(size>MAX_SIZE){
return malloc(size);
}
int index=0;
for(size_t tsize=MIN_SIZE; tsize<=MAX_SIZE; tsize*=2){
if(tsize>=size)break;
++index;
}
return pChunkList[index]->malloc();
}
void Free(void* pMem)
{
if(!free(pMem))MemoryChunk::free(pMem);
}
protected:
void* malloc(size_t size)
{
MemoryHeap* pHeap=(MemoryHeap*)::malloc(sizeof(HeapHeader)+size);
if(pHeap){
pHeap->header.size=size;
return &pHeap->pBuffer;
}
return NULL;
}
bool free(void* pMem)
{
MemoryHeap* pHeap=(MemoryHeap*)((char*)pMem-sizeof(HeapHeader));
if(pHeap->header.size>MAX_SIZE){
::free(pHeap);
return true;
}
return false;
}
private:
MemoryChunk** pChunkList;
int chunkcount;
};
#endif
ObejctManager.h文件,用於實現對象的創建與管理,比較簡易。
#ifndef OBJECT_MANAGER_H
#define OBJECT_MANAGER_H
#include "StaticMemory.h"
/** @class ObjectManager
* 實現利用內存池創建對象
* 要求對象具有缺省構造函數
*/
template<typename T>
class ObjectManager
{
public:
typedef T ObjectType;
static ObjectType* Create(StaticMemory* pool)
{
void* pobject=pool->Malloc(sizeof(T));
new(pobject) ObjectType();
return static_cast<ObjectType*>(pobject);
}
static void Delete(StaticMemory* pool, ObjectType* pobject)
{
pobject->~ObjectType();
pool->Free(pobject);
}
};
#endif
測試結果:
分單線程和多線程進行測試,重復的內存分配與釋放在實際使用中是不太可能的,為了模擬實際使用,通過隨機數來確定分配內存大小,同時也通過隨機數來確定分配與釋放操作。在測試過程中限制最大分配大小為1024,目的是為了測試小內存塊的分配情況對比。
內存池單線程測試結果
分配與釋放次數
malloc/free
內存池
100,000
0.01s
0.01s
1,000,000
0.15s
0.11s
10,000,000
1.26s
0.60s
100,000,000
9.21s
5.99s
1,000,000,000
92.70s
61.46s
內存池多線程測試結果
線程數目
malloc/free
內存池
1/1,000,000
0.15s
0.10s
2/1,000,000
1.49s
0.73s
4/1,000,000
9.945s
6.71s
8/1,000,000
45.60s
28.82s
進行多線程測試主要是測試多線程運行下,加鎖給內存分配帶來的影響,因此為了排除CPU的影響,測試采用的機器為16盒,16G內存的Linux服務器。
具體配置如下:
Intel(R) Xeon(R) CPU E5630 @ 2.53GHz
stepping : 2
cpu MHz : 2527.084
cache size : 12288 KB
------------------------------分割線------------------------------
C++ Primer Plus 第6版 中文版 清晰有書簽PDF+源代碼 http://www.linuxidc.com/Linux/2014-05/101227.htm
讀C++ Primer 之構造函數陷阱 http://www.linuxidc.com/Linux/2011-08/40176.htm
讀C++ Primer 之智能指針 http://www.linuxidc.com/Linux/2011-08/40177.htm
讀C++ Primer 之句柄類 http://www.linuxidc.com/Linux/2011-08/40175.htm
將C語言梳理一下,分布在以下10個章節中: