温馨提示:本文章略显恋爱脑,仅用于举例说明!!!切勿模仿!!!
1.什么是内存池
我们知道,我们程序中的变量函数等等都是占用内存的,那么如果每次在使用的时候都去向CPU内存去申请就很麻烦,为此,我们可以造一个"池子",先提前在里面申请足够大的内存,这样当我们需要内存时,就去这个池子里面申请就好了,如果不明白可以看下面这个例子,
就好比一对小情侣在恋爱的时候难免要约会吃饭,吃喝玩乐,这些都是要有金钱花费的,一种方法就是两个人AA,另一种方法就是开一个恋爱基金账户,提起在里面存好足够的钱,之后有共同花销时就直接去这个恋爱基金库里面拿钱,这里的恋爱基金库就对应的计算机领域的内存池!!!
2.创建内存池
了解内存池后,我们开始创建一个定长的内存池!这里的定长指的是它每次只能申请固定大小的内存,不能按需索取!
要创建内存池,首先要定义指向大块内存的指针(恋爱基金的账户号),大块内存的剩余内存大小(恋爱基金的剩余金额),以及归还回来的内存的管理方式,用一个freelist管理。
下面由于代码注释很详细,这里直接给出定长内存池代码!
template<class T>
class ObjectPool
{
public:
//申请内存函数
T* New()
{
//先定义obj对象
T* obj = nullptr;
//申请内存,先从freelist上面申请,不够了再去开大块空间
if (_freelist)
{
//有还回来的空间,且空间足够所需,
//"头删"一块内存给obj对象
void* next = *(void**)_freelist;
obj = (T*)_freelist;
_freelist = next;
}
else
{
// 剩余内存不够一个对象大小时,则重新开大块空间
if (_remainbytes < sizeof(T))
{
_remainbytes = 128*1024;//向大内存申请空间
_memory = (char*)malloc(_remainbytes);
if (_memory == nullptr)//检测申请成功与否
{
throw std::bad_alloc();
}
}
//内存现在申请成功了,准备给Obj对象分配
obj = (T*)_memory;
size_t ObjSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += ObjSize;
_remainbytes -= ObjSize;
}
// 定位new,显示调用T的构造函数初始化
new(obj)T;
return obj;
}
void Delete(T* obj)
{
// 显示调用析构函数清理对象
obj->~T();
//归还的内存以头插的方式由freelist管理
//头插
*(void**)obj = _freelist;
_freelist = obj;
}
private:
char* _memory = nullptr;// 指向大块内存的指针,使用char是方便后续进行内存分配
void* _freelist = nullptr;// 还回来过程中链接的自由链表的头指针,即还回来的内存用freelist仿照链表的形式
//来进行管理,
size_t _remainbytes = 0;// 大块内存在切分过程中剩余字节数
};
3.效率比较
随后进行效率比较:new和内存池效率(恋爱基金库和AA哪种方式支付的效率更高)
我们假设都是申请treenode节点若干次,比较二者时间即可,下面给出代码:
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
TreeNode()
:_val(0)
, _left(nullptr)
, _right(nullptr)
{}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds = 5;
// 每轮申请释放多少次
const size_t N = 100000;
std::vector<TreeNode*> v1;
v1.reserve(N);
//比较效率:new和内存池谁的效率更高
//new:
size_t begin1 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v1.push_back(new TreeNode);
}
for (int i = 0; i < N; ++i)
{
delete v1[i];
}
v1.clear();
}
size_t end1 = clock();
std::vector<TreeNode*> v2;
v2.reserve(N);
//内存池:
ObjectPool<TreeNode> TNPool;
size_t begin2 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v2.push_back(TNPool.New());
}
for (int i = 0; i < N; ++i)
{
TNPool.Delete(v2[i]);
}
v2.clear();
}
size_t end2 = clock();
cout << "new cost time:" << end1 - begin1 << endl;
cout << "object pool cost time:" << end2 - begin2 << endl;
}
二者时间如下:

我们发现有了内存池,效率大大提高了,就好比跟女朋友约会的花销,要是有恋爱基金的话,支付很快,刷一下卡就Ok,但是要是没有的话就要拿出计算器算两个人AA慢慢算了~会耽误约会时间~
最后代码汇总一下:
#pragma once
#include<iostream>
#include<vector>
#include<time.h>
//不要全部展开,会造成污染
using std::cout;
using std::endl;
#ifdef _WIN32
#include<windows.h>
#else
//
#endif
// 直接去堆上按页申请空间
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
// linux下brk mmap等
#endif
if (ptr == nullptr)
throw std::bad_alloc();
return ptr;
}
template<class T>
class ObjectPool
{
public:
//申请内存函数
T* New()
{
//先定义obj对象
T* obj = nullptr;
//申请内存,先从freelist上面申请,不够了再去开大块空间
if (_freelist)
{
//有还回来的空间,且空间足够所需,
//"头删"一块内存给obj对象
void* next = *(void**)_freelist;
obj = (T*)_freelist;
_freelist = next;
}
else
{
// 剩余内存不够一个对象大小时,则重新开大块空间
if (_remainbytes < sizeof(T))
{
_remainbytes = 128*1024;//向大内存申请空间
_memory = (char*)malloc(_remainbytes);
if (_memory == nullptr)//检测申请成功与否
{
throw std::bad_alloc();
}
}
//内存现在申请成功了,准备给Obj对象分配
obj = (T*)_memory;
size_t ObjSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
_memory += ObjSize;
_remainbytes -= ObjSize;
}
// 定位new,显示调用T的构造函数初始化
new(obj)T;
return obj;
}
void Delete(T* obj)
{
// 显示调用析构函数清理对象
obj->~T();
//归还的内存以头插的方式由freelist管理
//头插
*(void**)obj = _freelist;
_freelist = obj;
}
private:
char* _memory = nullptr;// 指向大块内存的指针,使用char是方便后续进行内存分配
void* _freelist = nullptr;// 还回来过程中链接的自由链表的头指针,即还回来的内存用freelist仿照链表的形式
//来进行管理,
size_t _remainbytes = 0;// 大块内存在切分过程中剩余字节数
};
struct TreeNode
{
int _val;
TreeNode* _left;
TreeNode* _right;
TreeNode()
:_val(0)
, _left(nullptr)
, _right(nullptr)
{}
};
void TestObjectPool()
{
// 申请释放的轮次
const size_t Rounds = 5;
// 每轮申请释放多少次
const size_t N = 100000;
std::vector<TreeNode*> v1;
v1.reserve(N);
//比较效率:new和内存池谁的效率更高
//new:
size_t begin1 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v1.push_back(new TreeNode);
}
for (int i = 0; i < N; ++i)
{
delete v1[i];
}
v1.clear();
}
size_t end1 = clock();
std::vector<TreeNode*> v2;
v2.reserve(N);
//内存池:
ObjectPool<TreeNode> TNPool;
size_t begin2 = clock();
for (size_t j = 0; j < Rounds; ++j)
{
for (int i = 0; i < N; ++i)
{
v2.push_back(TNPool.New());
}
for (int i = 0; i < N; ++i)
{
TNPool.Delete(v2[i]);
}
v2.clear();
}
size_t end2 = clock();
cout << "new cost time:" << end1 - begin1 << endl;
cout << "object pool cost time:" << end2 - begin2 << endl;
}
好了本篇文章到此结束,下一篇将正式进入内存池的项目练习,本篇属于开胃小菜
希望大家不要向博主一样恋爱脑,写博客还不忘了想女朋友,专心学习,相信能看到这里的应该都不缺对象了,因为可以随时随地的new一个出来嘛~
(开玩笑的,请勿当真,代码要好好写,恋爱也要好好谈哦~)