一、内存池
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一下的时候,采用内存池会节省大量的时间,否则会耗费更多的时间。
内存池可以防止更多的内存碎片的产生。
更方便于管理内存。