内存池

内存池 (Memory Pool)是一种内存分配方式。即可以分配内存。

为什么要用内存池?通常我们习惯直接使用new/deletemalloc/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结构取数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值