C++内存池设计与实现(一)

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
#include<windows.h>
#include<iostream>

using namespace std;

char buf[4100]; //Simple Memory Pool
#define MEMPOOL 0

#if MEMPOOL

class CTestClass
{
char m_chBuf[4096];
public:
void *operator new(unsigned int uiSize)
{
return (void *)buf;
}
void operator delete(void *p)
{
}
};

#else

class CTestClass
{
char m_chBuf[4096];
};

#endif

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的区别,看下图就会比较清楚了。
new操作示意图
图片来源于侯捷:Memory Pool的設計哲學和無痛運用
new运算符在这里完成了三个事情:

  • 调用operator new分配内存
  • 类型转换
  • 调用构造函数构造对象

而operator new有三种形式:

1
2
3
void* 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的一个重载,定义于中,它多接收一个ptr参数,但它只是简单地返回ptr。再来举个例子。

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
#include <iostream>
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可以是动态分配的内存,也可以是栈中缓冲。

清楚了上面这些,就可以开始我们的正式编写内存池了。

参考资料:

  1. http://blog.csdn.net/wudaijun/article/details/9273339
  2. http://blog.csdn.net/zhangxinrun/article/details/5940019
坚持原创技术分享,您的支持将鼓励我继续创作!