一、New delete和std::alloc
内存池就是预先分配好,放到进程空间的内存块,用户申请与释放内存其实都是在进程内进行,SGI-STL的alloc遇到小对象时就是基于内存池的。只有当内存池空间不够时,才会再从系统找一块很大的内存。
提到new和delete首先会转调用到malloc和free,这个大家应该很熟识了。很多人认为malloc是一个很简单的操作,其实巨复杂,它会执行一个系统调用(当然不是每一次,windows上是按页算),该系统调用会锁住内存硬件,然后通过链表的方式查找空闲内存,如果找到大小合适的,就把用户的进程地址映射到内存硬件地址中,然后释放锁,返回给进程。new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起。类似的,delete将对象析构和内存释放组合在了一起。我们分配单个对象时,通常希望将内存分配和对象初始化组合在一起。因为在这种情况下,我们几乎肯定知道对象应有什么值。当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象的创建操作(同时付出一定开销)。一般情况下,将内存分配和对象构造组合在一起可能会导致不必要的浪费。
二、New,delete调用与Allocate调用的效率对比
#include <QCoreApplication>
#include <QDebug>
#include <time.h>
#define RUNTIMES (10000)
struct CCC
{
CCC() {}
char data[10];
};
char test_new_delete()
{
char re = 0;
for (unsigned int i = 0; i < RUNTIMES; ++i)
{
for (unsigned int j = 0; j < RUNTIMES; ++j)
{
CCC* pc = new CCC();
CCC* pd = new CCC();
re += pc->data[2];
re += pd->data[2];
delete pc;
pc = NULL;
delete pd;
pd = NULL;
}
}
return re;
}
char test_allocator()
{
char re = 0;
std::allocator<CCC> alloc;
CCC* pStart = alloc.allocate(2);
for (unsigned int i = 0; i < RUNTIMES; ++i)
{
for (unsigned int j = 0; j < RUNTIMES; ++j)
{
CCC* pEnd = pStart;
alloc.construct(pEnd++, CCC());
re += pEnd->data[2];
alloc.construct(pEnd++, CCC());
re += pEnd->data[2];
while(pEnd != pStart)
{
alloc.destroy(--pEnd);
}
}
}
alloc.deallocate(pStart, 2);
return re;
}
#include<iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
double re1 = 0;
double re2 = 0;
// 运行内存池测试时,基本上对我机器其它进程没什么影响
time_t begin = time(0);
re1 = test_allocator(); // 使用内存池
time_t seporator = time(0);
// 运行到系统调用测试时,感觉机器明显变慢,
// 如果再加上内存碎片的考虑,对其它进程的影响会更大。
std::cout << long(seporator - begin) << std::endl;
re2 = test_new_delete(); // 直接系统调用
std::cout << long(time(0) - seporator) << std::endl;
std::cout << re1 << re2 << std::endl;
std::cout << "Test End!" << std::endl;
return a.exec();
}
在一个100000000次的循环中,使用内存池是3秒,使用系统调用是100秒。而且以上测试还没有考虑到碎片的影响,以及运行该程序时对其它程序的影响。而且还有一点,就是机器的内存硬件容量越大,内存分配时,需要搜索的时间就可能越长,如果内存是多条共同工作的,影响就再进一步。当然,有些情况是不可说用什么就用什么的,但如果可选的话,优先使用栈上的对象,其次考虑内存池,然后再考虑系统调用。
在stl-sgi中函数alloc::allocate() 和 alloc::deallocate()源代码在 <stl_alloc.h>中,SGI STL 设计了双层级配置器。第一层配置器直接使用malloc() 和 free().第二层配置器则视情况采用不同的策略:但配置区块超过 128 bytes时,调用第一级配置器。当配置区块小于 128 bytes时,采用复杂的 memory pool 方式。下面我们分别简单的介绍一下第一级和第二级配置器。
三、第一级__malloc_alloc_template