为什么要用内存池?通常我们习惯直接使用new/delete、malloc/free来申请/释放内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。特别所分配的内存很小时,容易产生内存碎片。
操作系统分配/释放内存的原理:分配内存时,操作系统会查找内存空闲表,找到一个比需要分配内存更大的表项,在分配所需大小的内存(此处可能会切割内存,因为此表项的内存一般都大于所需要分配的内存)。回收内存:将内存加入到内存空闲表中,此时有可能要合并内存。总结:操作系统在分配/回收内存都比较耗时,在效率要求较高的系统总,可能就会造成系统瓶颈,所以内存池技术就应运而生。
内存池实用的地方:1)适用于每次分配内存大小相同 2)申请/释放内存频繁 3)当然你所做的东西对效率的要求很高。
内存池为何快:1)对于平凡分配内存的情况,内存池一次向操作系统申请很多内存(预留空间),只需查询内存空闲表一次。每次分配时直接从预留空间分配,省去了查表时间,是不是很快? 2)对于频繁分配/释放的情况,在释放内存的时候,没有必要还给操作系统,而是还给内存池(因为后面还可能需要分配),下次要分配的时候就可以将回收的拿去分配,这样就节省了很多时间。
内存池组织方式:模拟操作系统组织空闲内存表,分配时空闲表不为空,则直接从表头取出分配出去。如果空闲表为空,就需要向操作系统申请一块大的空间,并组织成空闲表后,在分配。回收内存只需加入到空闲表即可。
说了这么多,还是用程序来说话:
在程序中用到两个结构体:memNode将空闲表节点组织成链表。memBlock:由于可能要多次向操作系统申请内存,需要将申请的空间串联成链表,最后释放整个内存池时便于释放。分清两个链表的不同,很重要。
技巧:我们需要将内存池中的内存组织成链表,就需要next指针,如果加next指针每个表项消耗4B内存,这样消耗非常大。由于空闲链表内存未使用,可以借用4B存储next域。而分配的时候,要从链表中取出节点,next域不在使用,借用的4B就还还回去了哦,这样就节省了额外的4B内存。由于要借用4B,所以内存池(我写这个版本)强制要求内存池没次分配大小不小于4B。
class memPool
{
private:
struct memNode
{
memNode* next;;
};
struct memBlock //向内存池加入内存时的数据结构
{
memBlock* next;
void* trueMem;
};
int size; //每次分配内存的大小
int groCnt; //向内存池加入内存应非配的大小
memBlock* blockList; //加入内存池的内存用链表连接,便于后面清空所有分配的内存
memNode* freeList; //内存池可分配内存的链表
public:
memPool(int s) : size(s),groCnt(1),blockList(NULL),freeList(NULL) //分配至少为4字节
{
if(s < 4)
{
size = 4;
}
}
virtual ~memPool()
{
memBlock* p;
while(blockList)
{
// cout << "清空" << endl;
p = blockList;
blockList = blockList->next;
free(p->trueMem);
free(p);
}
freeList = NULL;
}
void* Malloc()
{
if(freeList == NULL) //内存池中没有可用内存
{
// cout << "重新分配" << groCnt << endl;
memBlock* p = (memBlock*)malloc(sizeof(memBlock));
p->trueMem = malloc(groCnt * size);
//将p放入到链表中
p->next = blockList;
blockList = p;
char* pByte = (char*)(p->trueMem); //按字节访问真实分配的内存空间
freeList = (memNode*)pByte; //将新分配的内存加入到freeList中
for(int i = 1; i < groCnt; ++i)
{
((memNode*)pByte)->next = (memNode*)(pByte + size);
pByte += size;
}
((memNode*)(pByte))->next = NULL; //末尾节点
groCnt <<= 1;
}
void* p = (void*)freeList;
freeList = freeList->next;
return(p);
}
void Free(void* p)
{
((memNode*)p)->next = freeList;
freeList = (memNode*)p;
}
};
经我测试:内存池效率比操作系统分配效率要高10倍左右。
内存池的经典应用在于STL的内存分配子上应用,有兴趣的朋友可以看看源码。顺便说下台湾著名作家侯捷的《STL 源码剖析》对STL的内存分配子,进行了详细的分析,可以看看。
内存池分析到此结束。接下来说说new/delete,malloc/free的差别。
new:先调用operate new函数分配内存,在调用分配类型对应的构造函数,当然内置类型不用
free:先调用析构函数,在调用operate delete函数回收。
Malloc:只分配内存,不调用构造函数
Free:只回收内存
综上所述: new/delete由于多了一个步骤效率低于malloc/free,但是安全性高于高于他。
最后再说说指针类型的意义:指针的类型用在需要取数据时,按照被指向数据的类型来偏移取出相应的数据。而任何一种类型的指针在32位机上都是4B。
比如:struct node{
int n; //4B
node* next; //4B
}
例子:
Node* p = new node;
p->next; //在取next域时,关键就再域p的类型,因为p指向node类型,故要取next时,p要偏移4B,来取出next.
所以指针类型可以任意转换,不同类型的指针表现出的不同在于,取出数据时:int*会按照int的结构相应取数据,而node*则按照node结构取数据。