参考:
https://blog.csdn.net/K346K346/article/details/49538975
https://blog.csdn.net/xjtuse2014/article/details/52302083#comments
提高c++性能的编程技术 https://blog.csdn.net/fengbingchun/article/details/84497625
https://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html
内存池简介
内存池是池化技术中的一种形式。通常我们在编写程序的时候回使用 new delete 这些关键字来向操作系统申请内存,而这样造成的后果就是每次申请内存和释放内存的时候,都需要和操作系统的系统调用打交道,从堆中分配所需的内存。如果这样的操作太过频繁,就会找成大量的内存碎片进而降低内存的分配性能,甚至出现内存分配失败的情况。
而内存池就是为了解决这个问题而产生的一种技术。从内存分配的概念上看,内存申请无非就是向内存分配方索要一个指针,当向操作系统申请内存时,操作系统需要进行复杂的内存管理调度之后,才能正确的分配出一个相应的指针。而这个分配的过程中,我们还面临着分配失败的风险。(和os打交道很麻烦,会有额外开销,直接在内存池中申请内存)
所以,每一次进行内存分配,就会消耗一次分配内存的时间,设这个时间为 T,那么进行 n 次分配总共消耗的时间就是 nT;如果我们一开始就确定好我们可能需要多少内存,那么在最初的时候就分配好这样的一块内存区域,当我们需要内存的时候,直接从这块已经分配好的内存中使用即可,那么总共需要的分配时间仅仅只有 T。当 n 越大时,节约的时间就越多。
从大小的角度分为以下两种:
(1)、固定大小:分配固定大小内存块的内存管理器。
(2)、可变大小:分配任意大小内存块的内存管理器。所请求分配的大小事先是未知的。
类似的,从并发的角度也分为以下两种:
(1)、单线程:内存管理器局限在一个线程内。内存被一个线程使用,并且不越出该线程的界限。这种内存管理器不涉及相互访问的多线程。
(2)、多线程:内存管理器被多个线程并发地使用。这种实现需要包含互斥执行的代码段。无论什么时候,只能有一个线程在执行一个代码段。
经典内存池的设计
(1)先申请一块连续的内存空间,该段内存空间能够容纳一定数量的对象;
(2)每个对象连同一个指向下一个对象的指针一起构成一个内存节点(Memory Node)。各个空闲的内存节点通过指针形成一个链表,链表的每一个内存节点都是一块可供分配的内存空间;
(3)某个内存节点一旦分配出去,从空闲内存节点链表中去除;
(4)一旦释放了某个内存节点的空间,又将该节点重新加入空闲内存节点链表;
(5)如果一个内存块的所有内存节点分配完毕,若程序继续申请新的对象空间,则会再次申请一个内存块来容纳新的对象。新申请的内存块会加入内存块链表中。
// mempool.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <cassert>
#include <ctime>
using namespace std;
const int max = 10000000;
template<int size_obj,int Num_obj=10>//前面是对象的字节数,后面是每一个块中节点(对象)数量
class MemoryPool
{
public:
MemoryPool()noexcept;
~MemoryPool() noexcept;
void* alloc();
void expand_freelist();
void free(void*p);
private:
struct FreeNode//空闲节点 链表
{
FreeNode*next;
char val[size_obj];//节点数据域大小和对象size一样
};
struct MemBlock//块结构体 链表
{
FreeNode val[Num_obj];
MemBlock *next;
};
FreeNode *freenodehead;//当前第一个能用的空闲节点
MemBlock *memblockhead;//当前第一个能用的内存块
};
template<int size_obj, int Num_obj >
MemoryPool<size_obj, Num_obj>::MemoryPool() noexcept
{
freenodehead = nullptr;
memblockhead = nullptr;
}
template<int size_obj, int Num_obj >
MemoryPool<size_obj, Num_obj>::~MemoryPool() noexcept
{
MemBlock *ptr;
while (memblockhead)
{
ptr = memblockhead->next;
delete memblockhead;
memblockhead = ptr;
}
}
template<int size_obj, int Num_obj >
void *MemoryPool<size_obj, Num_obj>::alloc()
{
if (freenodehead==nullptr) //如果没有空闲队列了
{
expand_freelist();
}
void *ptr = freenodehead;
freenodehead = freenodehead->next;
return ptr;
}
template<int size_obj, int Num_obj >
void MemoryPool<size_obj, Num_obj>::expand_freelist() //没有空闲链表了 新创建块和链表 新创建的块插到头部
{
//块之间也是链表
//不用担心快之间链表没有联系 执行释放的话 会把他们连在一起的
MemBlock *newblock = new MemBlock();
newblock->next = nullptr;
freenodehead = &newblock->val[0];//新建的块的元素赋值给空闲节点
//把新建的节点连起来(释放之后 连的顺序就不一定了)
for (int i = 0; i < Num_obj-1; i++)
{
newblock->val[i].next = &newblock->val[i+1];
}
newblock->val[Num_obj - 1].next = nullptr;
if (memblockhead == nullptr) //没有第一个块 他就是第一个
{
memblockhead = newblock;
}
else//已经有了 把他插入到第一个
{
newblock->next = memblockhead;
memblockhead = newblock;
}
};
template<int size_obj, int Num_obj >
void MemoryPool<size_obj, Num_obj>::free(void*p)
{
FreeNode *ptr = (FreeNode*)p;
ptr->next = freenodehead;
freenodehead = ptr;
};
class Test//测试类
{
public:
Test() {};
explicit Test(int x):val(x) {};
~Test() {};
private:
int val;
};
int main()
{
clock_t start;
MemoryPool<sizeof(int)> mempool;
start = clock();
for (int i = 0; i < max; i++)
{
//Test *t1 = (Test*)mempool.alloc();
int *t1 = (int*)mempool.alloc();
//p1->print();
mempool.free(t1);
}
std::cout << "new Allocator Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
start = clock();
for (int i = 0; i < max; i++)
{
//Test *t2 = new Test();
int *t2 = new int();
delete t2;
}
std::cout << "old Allocator Time: ";
std::cout << (((double)clock() - start) / CLOCKS_PER_SEC) << "\n\n";
return 0;
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门提示:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
new Allocator Time: 0.375
old Allocator Time: 2.599