STL:二级空间配置器浅谈

我们在编写程序过程中,需要内存时,我们第一反应就是malloc,但是这样容易产生内片无法被利用。

在STL中提到了空间适配器,它主要分为两级:一级空间适配置器,二级空间配置器。一级空间适配器是对malloc的简单包装,它内部的allocate()和reallocate()都是在调用malloc()和realloc()不成功后,再调用oom_malloc()和oom_realloc()【清理内存】,重复多次后,如果还不成功便调用_THROW_BAD_ALLOC,丢出bad_alloc异常信息。

二级空间配置器:

二级空间配置器对内存的管理减少了小二区块造成的内存碎片,它主要是:如果所要申请的空间大于128字节,则直接交至以及空间配置器处理,如果小于128字节,则使用二级空间配置器,它是用一个16个元素的自由链表来管理的,每个位置下挂载着大小(分别为8、16、24、32、48、56、64、72、80、88、96、104、112、120、128字节),每次将所需内存提升到8的倍数。

free_list它的节点结构为一个共用体:

union obj
{
    union obj * free_list_link;
    char client_data[1];
};

二级空间配置器结构入下图所示:

自由链表中分出区块:

这样在完成工作的同时更加节省空间。在空间没有分配出去之前,该空间中一直存放的是所指向的下一个节点的地址,当该空间分配出去时,让对应的my_free_list指向它的下一个节点,然后将他内部的值改为要放入用户的data值,

它的内存分配主要分为以下四种情况:

1、所需内存下连接的区块和内存池其中一个为空

(1)、当内存池为空,但是所需空间大小提升为8的倍数后(入需要13bytes空间,我们会给它分配16bytes大小)所对应的free_list不为空时,它会像上图所示那样直接从对应的free_list中拔出。

if(n > __MAX_BYTES)          //所需内存是否大于128bytes,如果大于128bytes则调用以及空间配置器。
{
    return malloc_alloc::allocate(n);
}
//如果小于128bytes,调用二级空间配置器并找出16个free_list中时当的一个
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
//如果free_list中为空则准备填充(填充函数就如下面几种情况,下面会详细解释)
if(result == 0
{
    void *r = refill(ROUND_UP(n));
    return r;
}
//相应free_list有自由区块则调整free_list,拔除一个节点分配出去。
*my_free_list = result->free_list_link;
return result;

(2)、当对应的free_list为空,但是内存池不为空时:

(a)先检验它剩余空间是否够20个节点大小(即 所需内存大小(提升后) * 20),则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下。

//在调用时shunk_alloc()函数中参数nobjs值为20.
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int &nobjs)
{
    char *result;
    size_t total_bytes = size * nobjs;    //内存池中分配的总内存大小
    size_t bytes_left = end_free - start_free; //内存池中剩余空间大小
//判断内存池中剩余空间是否够需要的总空间
    if(bytes_left >= total_bytes)
    {
        //如果够的话,则改变内存池的起始指针指向,让它指向此时内存池的首地址,并将分配出去的空间首地址(即内存池原来起始地址)返回
        result = start_free;
        start_free += total_bytes;
        return result;
    }

将分配的首地址返回后,拿出一个节点分配给用户使用,剩余的19个节点挂在相应的free_list中

(b)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中

else if(bytes_left >= size)     //判断内存池中剩余内存大小够不够一个节点空间
{
    nobjs = bytes_left / size;  //计算内存池中剩余空间最多可以分配出多少个节点
    total_bytes = size * nobjs; //计算出内存池一共需要分配出多大内存
    result = start_free;        //记录分配出去空间的首地址
    start_free += total_bytes;  //改变内存池起始地址的指向
    return result;              //返回申请内存的首地址
}

(c)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中【肯找到相应的free_list,因为每次给内存池申请空间都是申请8的倍数,分配时也是以8的倍数为单位,所以内存池中此时剩余的内存也肯定是8的倍数】,然后载给内存池申请内存(第二种情况)。

2、内存池为空,所需要空间大小下连接的区块也为空

此时二级空间配置器会使用malloc()从heap上申请内存,但它一次所申请的内存大小为2倍的20个所要的内存大小(即 2 * 所需节点内存大小(提升后)* 20),申请成功后,将其中一半(即20节点空间)内存放入内存池中,然后从剩下的一半中拿出一个节点空间大小分配给用户,剩下的19个空间节点挂在载相应的free_list下。【之所以二级空间配置器每次申请40倍所需节点大小空间,是为了避免不停从heap上申请内存的麻烦,但然,之所以选择40倍的节点大小,这是编译者的一个经验值。】

3、malloc没有成功

在第二种情况下,如果malloc()失败了,说明heap上没有足够空间分配给我们了,这是,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从任意一个比它所需节点空间大的free_list中拔除一个节点来使用。

4、查找失败,调用一级空间配置器

第三种情况下,如果查找失败了,说明比他大的free_list中都没有自由区块了,此时会调动一级空间配置器oom_allocate(),它的原理手机清理内存差不多,它是将系统中能够回收的内存回收,然后为二级空间适配器所使用。

如果oom_allocate()也失败了,那说明内存已经山穷水尽了,这是只能返回“out_of_memory!”咯。

//如果内存池中剩余的空间连一个结点都不够,则先将内存池中剩余空间挂在在相应free_list下以防这块内存丢失,然后从heap上申请40倍节点空间
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
if(bytes_left > 0)        //判断内存池中是否有空间剩余
{
    //如果有,找出剩余内存所对应free_list中,将其挂在它下面,
    obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
    ((obj*)start_free)->free_list_link = *my_free_list;
    *my_free_list = (obj*)start_free;
}
//让内存池的起始指针指向心申请的空间的首地址
start_free = (char *)malloc(bytes_to_get);
if(0 == start_free)        //判断是否申请失败
{
   //情况3:malloc()失败
   //如果申请失败
    int i;
    obj * volatile * my_free_list, *p;
    //从大于size开始的free_list中一一查找,看是否还有没有分配出去的区块
    for(i = size; i<=__MAX_BYTES; i+=__ALIGN)
    {
         my_free_list = free_list + FREELIST_INDEX(i);
         p = *my_free_list;
         if(0 != p)
         {   //找到后,拔除一个区块来使用,从中拿出size大小分配给用户,剩余的当作内存池空间
             *my_free_list = p->free_list_link;
             start_free = (char *)p;
             end_free = start_free + i;
             return chunk_alloc(size,nobjs);
         }
     }

第4种情况:查找失败:
     //调用一级空间配置器
     end_free = 0;
     start_free =(char*)malloc_alloc::allocate(bytes_to_get);
}   //如果时内存不足的清况有所改善皆大欢喜,如果不能成功则抛出异常“out_of_memory!”

heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
//递归调用自己,尽可能的改善这一情况
return chunk_alloc(size,nobjs);

空间回收问题:

当用户从二级空间配置器中申请的内存被释放时,二级空间配置器要将其回收然后插入对应的free_list,其过程如下图所示:

static void deallocate(void *p,size_t n)
{
    obj *q = (obj *)p;
    obj * volatile * my_free_list;
    //判断所释放内存是否大于128bytes
    if(n > __MAX_BYTES)
    {
        //如果大于128bytes,则用一级空间配置器回收
        malloc_alloc::deallocate(p,n);
        return;
    }
    //如果小于128bytes,则使用二级空间配置器回收。
    my_free_list = free_list + FREELIST_INDEX(n);
    q->free_list_link = *my_free_list;
    *my_free_list = q;
}

这就是二级空间配置器的示现过程,以及申请过程中各种情况的申请的解决放法,因为主要是总结思想,所以代码比较零散,如果需要完整代码,可以查看sgi-STL源码库.

源码下载地址:http://www.sgi.com/tech/stl/download.html

时间: 2024-10-02 23:44:39

STL:二级空间配置器浅谈的相关文章

STL 之 空间配置器(allocator)

一.SGI 标准的空间配置器,std::allocator SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳. 它只是把C++的操作符::operator new和::operator delete做了一层简单的封装而已. 二.SGI 特殊的空间配置器,std::alloc 由于SGI 标准的空间配置器只是把C++的操作符::operator new和::operator delete做了一层简单的封装,没有考虑到任何效率上的

(一)STL剖析——空间配置器

Alloc.h //Alloc.h负责内存空间的配置与释放 //Construct.h负责对象内容的构造与析构 //这两个头文件在memory文件中包含 #pragma once typedef void(*HANDLER_FUNC)(); //一级空间配置器 template <int inst> class __MallocAllocTemplate  { public: static void* Allocate(size_t size)//静态成员函数 { void* result =

C++标准库——STL之空间配置器

声明:源码同<STL源码剖析>(侯捷) STL: C++标准的模板库,通用性高. 常见的数据结构封装. 提供常用的通用算法. STL六大组件: 容器      算法       迭代器       仿函数(函数对象)     适配器      配置器 空间配置器的作用: 1.提高代码复用率,功能模块化. 2.减少内存碎片问题. 比如我们list是链式结构,如果其中的成员是一个个new出来的,我们知道new的底层实现是malloc,同时也必须清楚,当malloc一块40字节空间时,操作系统为了能

STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator delete().这两个全局函数相当于C的malloc() 和free() 函数.而SGI正是以malloc() 和free() 完成内存的配置与释放. 考虑到小型区块可能造成的内存破碎问题,SGI设计了两级的空间配置器.第一级直接使用malloc() 和free() ,而第二级则视情况采用不同的策略:当

【STL】空间配置器剖析(二)

上篇文章点击打开链接主要对于对象的构造含和析构进行了主要说明,这篇文章将对对象构造前的内存配置和对象析构后的空间释放进行深入探索. 好的,话不多说马上进入是正文: 对对象构造前的内存配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下: 向system heap要求空间. 考虑多线程的状态 考虑内存不足的应变措施 考虑过多的"小型区块"可能造成的内存碎片问题 C++的内存配置的基本操作是:operator new(),内存释放的基本操作是o

STL空间配置器那点事

STL简介 STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合. 谈及组件,那么我们就首先来简单谈下STL六大组件,其相关的设计模式使用,以及各组件之间的协作关系. 设计模式一览 六大组件简单介绍 1. 空间配置器:内存池实现小块内存分配,对应到设计模式--单例模式(工具类,提供服务,一个程序只需要一个空间配置器即可),享元模式(小块内存统一由

stl空间配置器线程安全问题补充

摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关于STL空间配置的效率以及空间释放时机做了简单的探讨. 线程安全问题概述 为什么会有线程安全问题? 认真学过操作系统的同学应该都知道一个问题. first--进程是系统资源分配和调度的基本单位,是操作系统结构的基础,是一个程序的运行实体,同时也是一个程序执行中线程的容器 seconed--进程中作为资源分配基

stl 空间配置器理解

理解了一下stl的空间配置器,发现一个比较好的学习方法,跟着代码自己也跟着写一遍,顺便加些注释,可以更加帮助自己理解. 如new,delete一般,分为两个步骤,1,配置空间,2,构造对象(1,析构对象,2,释放空间) 一.构造和析构的基本工具(construct,destroy) 1,construct(构造) template<class T1,class T2> inline void construct(T1 * p,const T2 & value){ new (p) T1(

2.SGI STL第二级空间配置器__default_alloc_template的chunk_alloc函数

SGISTL默认使用二级空间配置器,当需要配置的区块大于128 bytes时SGI STL调用一级空间配置器,一级空间配置器的allocate函数直接使用malloc分配内存,deallocate函数直接使用free释放内存.当需要配置的区块小于128 bytes时SGI STL调用二级空间配置器. 相比于一级空间配置器简单粗暴的内存使用方法,二级空间配置器对内存的使用显得精细很多. 二级空间配置器的具体用法请看书,我就不抄书了,只对二级空间配置器中容易糊涂的地方写一下我的理解. 内存池和fre