C++内存池实现
什么是内存池
(Memory Pool)是一种内存分配方式。通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。使用该技术的优点是重用现有的内存块,以便减少系统调用的时间。
为什么使用内存池
主要有两点:
- 缩短的程序分配内存的时间
- 避免内存碎片
通过下面的例子体会一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using namespace std;
char buf[4100]; //Simple Memory Pool
class CTestClass
{
char m_chBuf[4096];
public:
void *operator new(unsigned int uiSize)
{
return (void *)buf;
}
void operator delete(void *p)
{
}
};
class CTestClass
{
char m_chBuf[4096];
};
int main()
{
DWORD count = GetTickCount();
for(unsigned int i=0; i<0xfffff; i++)
{
CTestClass *p = new CTestClass;
delete p;
}
cout << "Interval = " << GetTickCount()-count << " ms" << endl;
cin.get();
return 0;
}
使用内存池的耗时为78 ms,而直接new的耗时为5320 ms。
插入科普
这里有必要科普一下new和operater new的区别,看下图就会比较清楚了。
图片来源于侯捷:Memory Pool的設計哲學和無痛運用
new运算符在这里完成了三个事情:
- 调用operator new分配内存
- 类型转换
- 调用构造函数构造对象
而operator new有三种形式:1
2
3void* operator new (std::size_t size) throw (std::bad_alloc); //throwing方式
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw(); //nothrow方式
void* operator new (std::size_t size, void* ptr) throw(); //placement方式
其中throwing方式和nothrow方式的区别仅仅是否抛出异常,当分配失败时,前者会抛出bad_alloc异常,后者返回null,不会抛出异常。它们都分配一个固定大小的连续内存。
而placement new,它也是对operator new的一个重载,定义于1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A
{
public:
A()
{
std::cout<<"call A constructor"<<std::endl;
}
~A()
{
std::cout<<"call A destructor"<<std::endl;
}
};
int main()
{
//char buf[100];
//A* p = new(buf) A(); //buf上分配
A* p = (A*)::operator new(sizeof(A)); //分配
new(p) A(); //构造
p->~A(); //析构
::operator delete(p); //释放,在buf上分配的话不需要释放
system("pause");
return 0;
}
在这里,operator new(size_t, void*)并没有什么作用,真正起作用的是new运算符的第二个步骤:在p处调用A构造函数。这里的p可以是动态分配的内存,也可以是栈中缓冲。
清楚了上面这些,就可以开始我们的正式编写内存池了。