内存池的实现

一、内存池

1、内存池的概念

        内存池(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。

  内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升。

2、内存池的流程和设计

1. 先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象。

2. 每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间。

3. 某个内存节点一旦分配出去,从空闲内存节点链表中去除。

4. 一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表。

5. 如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。

3、内存池的结构示意图

4、code

#include<iostream>

using namespace std;

template<int ObjectSize,int NumofObjects = 20>
/*ObjectSize:内存块中每个节点的大小
NumofObject:每个内存块的节点的个数
*/
class MemPoll
{
private:
	struct FreeNode//节点
	{
		FreeNode* next;
		char data[ObjectSize];//这个节点的大小,即使用这块内存的大小
	};

	struct MemBlock//内存块
	{
		MemBlock* next;
		FreeNode data[NumofObjects];//默认每个内存快有20个节点
	};

	FreeNode* nodeheader;//指向当前待分配的空闲节点
	MemBlock* memblockheader;//指向当前最新的内存块

public:
	MemPoll()
	{
		nodeheader = nullptr;
		memblockheader = nullptr;
	}
	~MemPoll()//依次去释放内存块
	{
		MemBlock* cur;
		while (memblockheader)
		{
			cur = memblockheader->next;
			delete memblockheader;
			memblockheader = cur;
		}
	}
	void* malloc();
	void free(void*);
};

template<int ObjectSize,int NumofObjects>
void* MemPoll<ObjectSize, NumofObjects>::malloc()//开辟内存时
{
	if (nodeheader == nullptr)//如果此时无空闲节点
	{
		MemBlock* newBlock = new MemBlock;//开辟一个内存块
		newBlock->next = nullptr;
		nodeheader = &newBlock->data[0];//新开辟内存块的第一个空闲节点
		for (int i = 1; i < NumofObjects; ++i)
		{
			newBlock->data[i - 1].next = &newBlock->data[i];
		}
		newBlock->data[NumofObjects - 1].next = nullptr;//将内存块中的这些节点连接起来

		if (memblockheader == nullptr)//说明此时为首次申请内存块
		{
			memblockheader = newBlock;
		}
		else//否则将之前的内存块和新开辟的内存块连接起来
		{
			newBlock->next = memblockheader;
			memblockheader = newBlock;
		}
	}
	void* cur = nodeheader;
	nodeheader = nodeheader->next;
	return cur;//返回一个空闲的节点
}

template<int ObjectSize, int NumofObjects>
void MemPoll<ObjectSize, NumofObjects>::free(void* p)
{
	//将这个不被使用的节点归还给内存块
	FreeNode* cur = (FreeNode*)p;
	cur->next = nodeheader;
	nodeheader = cur;
}

class ActualClass
{
private:
	static int count;
	int No;
public:
	ActualClass()
	{
		No = count;
		count++;
	}

	void Print()
	{
		cout << this << ":" << "the" << No << "th object" << endl;
	}

	void* operator new(size_t size);
	void operator delete(void* p);
};
int ActualClass::count = 0;
MemPoll<sizeof(ActualClass), 2> mp;

void* ActualClass::operator new(size_t size)
{
	return mp.malloc();
}

void ActualClass::operator delete(void* p)
{
	mp.free(p);
}

int main()
{
	ActualClass* p1 = new ActualClass;
	p1->Print();

	ActualClass* p2 = new ActualClass;
	p2->Print();
	delete p1;

	p1 = new ActualClass;
	p1->Print();

	ActualClass* p3 = new ActualClass;
	p3->Print();

	delete p1;
	delete p2;
	delete p3;
	system("pause");
	return 0;
}

4、内存池的特点

针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法和多线程保护。也不需要维护内存空闲表的额外开销,从而获得较高的性能。

由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。

比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。

当需要分配管理的内存在100M一下的时候,采用内存池会节省大量的时间,否则会耗费更多的时间。

内存池可以防止更多的内存碎片的产生。

更方便于管理内存。

Guess you like

Origin blog.csdn.net/ThinPikachu/article/details/121191193