实现一个简单的定长内存池

一、内存池

内存池是一种动态内存分配与管理技术,程序员习惯直接使用new、delete、free、malloc等这些API申请和释放内存,这样导致的后果:由于申请的大小不固定,频繁的使用会造成内存碎片从而降低程序和操作系统的性能。内存池则是在使用时,先申请一大块内存留做备用,当程序需要使用时,就直接使用内存池中的内存,当程序要将内存释放时,就先释放到内存池中。当再次申请时,就再次从内存池中取出使用。这样可以避免频繁的找系统申请,大大加快了系统的性能。

1.1、好处

  • 1、我们一次性向系统申请一大块内存,这样可以降低内存碎片问题。
  • 2、当我们已经使用过的内存释放过后就放入内存池中,第二次再次申请时我们直接往内存池取出内存,这样提高了内存的分配率。

1.2、定成内存池

下面我介绍的内存池是针对某种固定的对象使用的。但是原理和上面是一样的:一次性向系统申请一大块内存,然后将大块内存分别切割为对象大小的内存分配给对象使用。当这个对象释放后,我们将对象还回来的内存放到内存池中。下次要申请内存时直接从内存池中获取。

1.3、内存池的申请

在这里插入图片描述

我们使用链表把通过malloc一次性申请的内存管理起来,最后通过析构函数时,只需要释放链表即可。

1.4、自由链表的释放与管理

在这里插入图片描述

1.5、技巧

为了维护链表,每个free-list节点需要额外的指针(指向下一个节点),这不仅又造成另一种额外的负担吗?但是我们可以通过将对象的前4个字节或者8个字节用来做存区下一个指针的地址,就可以解决了。

	void* free_list;
	T* obj;
	//32位系统下:
	//将对象放回内存池
	*((int*)obj)=(int)free_list;
	free_list=obj;
	//从内存池中取内存
	obj=(T*)free_list;
	free_list=*((int*)obj);
	
	//64位系统下:
	//将对象放回内存池
	*((long*)obj)=(long)free_list;
	free_list=obj;
	//从内存池中取内存
	obj=(T*)free_list;
	free_list=*((long*)obj);

指针大小由于系统不同而占用的字节数是不同的,我们只能通过判断指针的大小来取对象的前多少个字节?没有其他方法?

void* free_list;
T* obj;
//将对象放回内存池
*((T**)obj)=(T)free_list;
free_list=obj;
//从内存池中取内存
obj=(T*)free_list;
free_list=*((T**)obj);


那么我们将对象转换为二级指针,这样我们在解引用时,指针是多大就解引用指针大小个字节的数,这样我们就解决了系统的不同带来的指针大小不同的问题。

但是如果当对象的大小小于对象指针大小的时候,也就是一个对象的空间存不下一个指针的大小。
例如:为char类型对象开辟内存池,sizeof(char)<sizeof(char*),这时候我们就要为一个char类型对象申请sizeof(char*)大小的内存。

二、内存池的设计步骤

1、初始化

在创建内存池的时候为内存池分配一大块初始内存,便于以后的使用。

2、分配内存

当需要内存的时候就去内存池里面分配内存。先查看自由链表中有无内存,如果没有就把大块内存进行切割为所需要内存大小。当大块内存切割完之后,重新申请,每次申请的内存是上一次申请的某倍数。如果自由链表中有内存就直接取自由链表中的内存。

3、回收内存

将内存挂到自由链表上。

4、释放内存池

将管理大块内存的链表依次释放。不用去管自由链表上的内存块。

C++代码实现

template<class T>
class ObjectPool {
	struct BlockNode {
		char* memery;	//记录当前大块内存的首地址
		BlockNode* next;	//连接下一块大块内存
		size_t objNum;		//当前大块内存最多能切出多少个对象
		BlockNode(size_t _objNum)
			:next(nullptr), objNum(_objNum)
		{	
			memery = (char*)malloc(objNum*itemSize);
		}
		~BlockNode() {
			free(memery);
			memery = nullptr;
			next = nullptr;
			objNum = 0;
		}
	};
public:
	ObjectPool(size_t initNum = 32, size_t _maxNum = 10000)
		:curCount(0), maxNum(_maxNum), freelists(nullptr)
	{
		head = new BlockNode(initNum);
	}

	~ObjectPool() {
		Destory();
	}
	//根据系统取对象前指针大小个字节
	T*& Next(T* obj) {
		return *((T**)obj);	
	}

	void Destory() {
		while (head) {	//释放所有通过malloc申请的大块内存
			BlockNode* cur = head;
			head = head->next;
			delete cur;
		}
		head = nullptr;
	}
	void Delete(T* obj) {
		obj->~T();	//调用对象的析构函数
		Next(obj) = freelists;	//通过头插将对象和自由链表连接起来
		freelists = obj;	
	}
	T* New() {
		T* obj = nullptr;
		if (freelists) {	//判断自由链表中是否有内存
			obj = freelists;		//从自由链表中取内存
			freelists = Next(freelists);	//自由链表移动到下一个自由链表
			new(obj)T;		//定位new  初始化对象
			return obj;
		}
		if (curCount >= head->objNum) {	//当前大块内存已被全部切割出去需要重新分配一个更大的内存
			size_t size = curCount * 2; //每次分配是原来的2倍
			if (size > maxNum) {	//当申请内存达到上限值时,就为上限值大小
				size = maxNum;
			}
			BlockNode* temp = new BlockNode(size);//重新申请大块内存
			temp->next = head;	//将申请的大块内存通过头插,插入到管理大块内存的链表中
			head = temp;	//头指针指向新申请的大块内存
			curCount = 0;
		}
		obj = (T*)(head->memery + itemSize * curCount);	//将大块内存切割为对象大小的内存
		curCount++;	//将当前大块内存分配分配出来的小块内存+1
		new(obj)T;	//定位new 调用对象的构造函数初始化对象
		return obj;
	}
protected:
	size_t curCount; //当前BlockNode已经分配了多少个对象
	size_t maxNum;	//BlockNode申请是以倍数增长的,这个变量限制最大能申请的内存大小,最大申请内存的上限值
	BlockNode* head;//大块内存的链表头
	T* freelists;	//自由链表
	static size_t itemSize;	//对象的大小;当对象的大小小于指针时,就取指针的大小,当指针大小小于对象大小时就取对象大小
	static size_t GetItemSize() {
		return sizeof(T) > sizeof(T*) ? sizeof(T):sizeof(T*);
	}
};

template<class T>
size_t ObjectPool<T>::itemSize = ObjectPool<T>::GetItemSize();

三、定长内存池的测试

void TestObjectPool() {
	size_t begin1 = clock();
	std::vector<char*> v1;
	for (int i = 0; i < 100000; ++i)
	{
		v1.push_back(new char);
	}
	for (int i = 0; i < 100000; ++i)
	{
		delete v1[i];
	}
	v1.clear();

	for (int i = 0; i < 100000; ++i)
	{
		v1.push_back(new char);
	}

	for (int i = 0; i < 100000; ++i)
	{
		delete v1[i];
	}
	v1.clear();
	size_t end1 = clock();


	ObjectPool<char> tnPool;
	size_t begin2 = clock();
	std::vector<char*> v2;
	for (int i = 0; i < 100000; ++i)
	{
		v2.push_back(tnPool.New());
	}
	for (int i = 0; i < 100000; ++i)
	{
		tnPool.Delete(v2[i]);
	}
	v2.clear();

	for (int i = 0; i < 100000; ++i)
	{
		v2.push_back(tnPool.New());
	}
	for (int i = 0; i < 100000; ++i)
	{
		tnPool.Delete(v2[i]);
	}
	v2.clear();

	size_t end2 = clock();

	cout <<"malloc函数2轮100000次申请和释放所用时间:" <<end1 - begin1 <<"ms"<< endl;
	cout << "定成内存池2轮100000次申请和释放所用时间:"<<end2 - begin2 << "ms"<<endl;

}

int main() {
	TestObjectPool();
	return 0;
}

在这里插入图片描述

通过测试我们还是能看出内存池的效率大大提高了。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值