STL空间配置器

来源:互联网 发布:面包车拉货软件 编辑:程序博客网 时间:2024/06/05 05:58

STL空间配置器

空间配置器

在STL中,STL的空间配置器是一个名为allocator的模板类,allocator有几个重要个接口,分别为allocator::allocate(),allocator::deallocate(),allocator::construct(),allocator::destroy()。上述所说的allocator只是基层内存配置/释放行为(也就是::operator new和::operator delete)的一层薄薄的包装,并没有考虑到任何效率上的强化,SGI另有法宝。SGISTL在这个项目上脱离了STL标准,使用一个专属的,拥有次层分配能力的、效率优越的特殊配置器。其名称为alloc而非allocator,而且不接受任何参数。所有SGI STL的容器默认的空间配置器为alloc。虽然SGI也定义有一个符合部分标准、名为allocator的配置器,但SGI从未使用过他,因为SGI的allocator只是在::operator new和::operator delete做了一层薄薄的包装而已,效率不佳。以下提到的allocator指STL 空间配置器,不是SGI的allocator。
    class foo{ . . . };    foo* pf=new foo; //配置内存,然后构造对象    delete pf; //将对象析构,然后释放内存
上面的new含有两个操作:(1)调用::operator new配置内存;(2)调用foo::foo()进行构造对象内容。delete也含有两个操作:(1)调用foo::~foo()析构对象;(2)调用::operator delete释放内存。

为了精密分工,STL的allocator决定将这两个阶段区分开来。内存配置操作由allocator::allocate()负责,内存释放操作由allocator::deallocate(),对对象构造操作由allocator::construct()负责,对对象析构操作由allocator::destroy()负责。
STL的配置器定义于之中,SGI内含有两个文件

    #include<stl_alloc.h>    #include<stl_construct.h>

构造和析构基本工具:construct()和destroy()

<stl_construct.h>中定义了construt()和destroy()两个函数,用来构造和析构对象。其中construt()使用placemen new运算子来完成。destroy()有两个版本,一个版本直接调用对象的析构函数即可,另一个需要将一个范围的的对象全部析构。但如果在一个范围内析构对象时,析构函数无关痛痒,多次调用析构函数会影响效率,这里destroy()通过_type_traits<>技术来判断应该在循环中对所指范围内的每一个对象调用destroy(),还是什么也不做就结束。以下是construct和destroy的示意图:这里写图片描述部分代码如下:
#include <new.h>        // 欲使用placement new,需先包含此文件template <class T1,class T2>inline void construct(T1 *p,const T2& value){    new (p) T1(value);//调用placement new ;}//以下是destroy()第一个版本,接受一个指针template <class T>inline void destroy(T* pointer) {pointer->~T(); //调用析构函数}// 如果元素的数值型别(value type)有 non-trivial destructor…template <class ForwardIterator>inline void__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {  for ( ; first < last; ++first)    destroy(&*first);}// 如果元素的数值型别(value type)有 trivial destructor…template <class ForwardIterator> inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}// 判断元素的数值型别(value type)是否有 trivial destructortemplate <class ForwardIterator, class T>inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;  __destroy_aux(first, last, trivial_destructor());}// 以下是 destroy() 第二版本,接受两个迭代器。此函数设法找出元素的数值型别//进而利用__type_traits<>求取最适当措施template <class ForwardIterator>inline void destroy(ForwardIterator first, ForwardIterator last) {  __destroy(first, last, value_type(first));}// 以下是destroy() 第二个版本针对迭代器char* 和 wchar_t* 的特化版inline void destroy(char*, char*) {}inline void destroy(wchar_t*, wchar_t*) {}

空间的配置与释放,std::alloc

        对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI对此的设计哲学如下:
  • 向system heap要求空间
  • 考虑多线程状态
  • 考虑内存不足的应变措施
  • 考虑过多“小型区块”可能造成的内存碎片问题
    为了简化问题,下面的代码排除了多线程状态的处理。
    考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层级配置器,第一级配置器直接使用了malloc()和free(),第二级配置器则视情况采用不同策略:当配置区块超过128bytes时,视之为“足够大”
    便使用第一级配置器;当配置区块小于128bytes时,视之为“过小”,为降低额外负担,便采用复杂的memory pool整理方式,而不再求助于第一配置器,整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于__USE_MALLOC是否被定义。两个配置器关系如下:
    这里写图片描述
    这里写图片描述

第一级配置器__malloc_alloc_template

第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置,释放,重配置操作,并以实现类似C++ new-handler的机制。 所谓C++new-handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指的函数。换句话说,一旦::operator new 无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用有客端制定的处理例程。该处理例程通常被称为new-handler。 注意,SGI 以malloc而非::operator new来配置内存,一个原因是历史因素,另一个原因是c++并未提供相应于realloc()的内存配置操作,因此,SGI 不能直接使用C++的new-handler(). 必须仿真一个类似的set_malloc_handler()。 请注意,SGI 第一配置器的allocate()和realloc()都是在调用malloc()和realloc()不成功后,该调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务,但如果“内存不足处理例程”并未被客户端设定,oom_malloc()和oom_realloc()便不老实地调用__THROW_BAD_ALLOC,丢出BAD_ALLOC异常信息,或利用exit(1)硬生中止程序。 部分代码如下:
    //注意:无“template”型别参数,非型别参数inst完全没有派上用场    template <int inst>    class _malloc_alloc_template{        private:        //以下三个函数用来处理内存不足情况            static void *oom_malloc(size_t);            static void *oom_realloc(void*,size_t);            #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG                static void (* __malloc_alloc_oom_handler)();            #endif        public:            static void* allocate(size_t n){                void *result=malloc(n);//直接使用malloc                if(result==0)result=oom_malloc(n);                return result;            }            static void deallocate(void *p,size_t){                free(p);//直接使用free            }            static void* reallocate(void *p,size_t new_size){                void *result=realloc(p,new_size);//第一级配置器直接使用realloc()                if(result==0)result=oom_realloc(p,new_size);                return result;            }            //以下类似C++的set_new_handler()            static void (* set_malloc_handler(void (*f)()))()            {                void (*old)()=_malloc_alloc_oom_handler;                _malloc_alloc_oom_handler=f;                return old;            }    };    //_malloc_alloc_oom_handler初值为0,给客户定义    #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG        template <int inst>        void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;    #endif    template <int inst>    void * _malloc_alloc_tempalte<inst>::oom_malloc(size_t n){        void (* my_malloc_handler)();        void *result;        for(;;){//不断尝试释放配置            my_malloc_handler=_malloc_alloc_oom_handler;            if(my_malloc_handler==0){ __THROW_BAD_ALLOC; }            (*my_malloc_handler)();//调用处理例程序            result=malloc(n);      //再次尝试内存配置            if(result!=0)return result;        }       }    template <int inst>    void * _malloc_alloc_template<inst>::oom_realloc(void *p,size_t n){        void (* my_malloc_handler)();        void *result;        for(;;){            my_malloc_handler=_malloc_alloc_oom_handler;            if(my_malloc_handler==0){ __THROW_BAD_ALLOC; }            (*my_malloc_handler)();            result=realloc(p,n);            if(result!=0)return result;        }       }    typedef __malloc_alloc_template<0> malloc_alloc;

第二级配置器 __default_alloc_template

第二级配置器由__default_alloc_template类负责,该配置器做法是:如果区块大于128bytes,就调用第一级配置器。当小于128bytes时,则以内存池管理,每次配置一大块内存,并维护对应的自由链表free-list。下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客户释放小额区块,就由配置器回收到free-lists中。为了管理方便,配置器会将主动将任何小额区块的内存需求量上调到8的倍数,并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,83,81,88,96,104,112,120,128bytes的小额区块。free-lists节点结构如下:
 union obj{     union obj* free_list_link;     char clinet_data[1];     };

图如下:
这里写图片描述

第二级配置器部分代码如下:

    template <bool threads,int inst>    class _default_alloc_tempalte{        private:            # ifndef __SUNPRO_CC                enum {__ALIGN = 8};     // 小型区块的上调边界                enum {__MAX_BYTES = 128};   // 小型区块的上限                enum {__NFREELISTS = __MAX_BYTES/__ALIGN};      // free-lists 个数            # endif            //ROUND_UP将bytes上调至8的倍数            static size_t ROUND_UP(size_t bytes){                return (bytes+__ALIGN-1)&~(__ALIGN-1);            }            union obj{                 union obj* free_list_link;                 char clinet_data[1];            };            # ifdef __SUNPRO_CC                static obj * __VOLATILE free_list[];            # else                static obj * __VOLATILE free_list[__NFREELISTS];            # endif            //根据区块大小,找到决定使用的free_list            static size_t FREELIST_INDEX(size_t bytes){                return (bytes+__ALIGN-1)/(__ALIGN-1);            }            //返回一个大小为n的对象,并可能加入大小为n的其他区块到free_list            static void *refill(size_t);            //配置一大块空间,可容纳nobjs个大小为"size"的区块            //如果配置nobjs个区块有所不便,nobjs可能会降低            static char *chunk_alloc(size_t size,int &nobjs);            static char *start_free;//内存池起始位置,只在chunk_alloc中变化            static char *end_free; //内存池结束为止,只在chunk_alloc中变化            static size_t heap_size; //分配堆的空间大小        public:            static void* allocate(size_t n){/*详述与后*/}            static void* deallocate(void *p,size_t n){/*详述与后*/}            static void* reallocate(void *p,size_t old_size,size_t new_size);    };    //下面是static data member的定义于初值设定    template <bool threads, int inst>    char *__default_alloc_template<threads, inst>::start_free = 0;    template <bool threads, int inst>    char *__default_alloc_template<threads, inst>::end_free = 0;        template <bool threads, int inst>    size_t __default_alloc_template<threads, inst>::heap_size = 0    template <bool threads, int inst>    __default_alloc_template<threads, inst>::obj * __VOLATILE    __default_alloc_template<threads, inst> ::free_list[    # ifdef __SUNPRO_CC        __NFREELISTS    # else        __default_alloc_template<threads, inst>::__NFREELISTS    # endif    ] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
 关于allocate函数
    static void* allocate(size_t n){        obj * volatile * my_free_list;        obj * result;        //大于128bytes就调用第一级配置器        if(n>(size_t) _MAX_BYTES)            return malloc_alloc::allocate(n);        //寻找16个free_lists中合适的一个        my_free_list=free_lists+FREELIST_INDEX(n);        result=*my_free_list;        if(result==0){        //如果没有找到就调用refill填充free_lists            void *r =refill(ROUND_UP(n));            return r;        }        *my_free_list=result->free_list_link;        return result;    }

如下图:
这里写图片描述
关于deallocate函数
代码如下:

    static void deallocate(void *p,size_t n){        opj *q=(obj *)p;        obj * volatile * my_free_list;        //如果大于128就调用第一级配置器        if(n>(size_t) __MAX_BYTES){            malloc_alloc::deallocate(p,n);            return;        }        //寻找对应的free_list        my_free_list = free_list+FREELIST_INDEX(n);        q->free_list_link=my_free_list;        *my_free_list=q;    }

如图所示:
这里写图片描述
重新填充refill函数
代码如下:

    template <bool threads,int inst>    void* __deault_alloc_template<threads,inst>::refill(size_t n){        int nobjs=20;        //调用chunk_alloc(),尝试取得nobjs个区块作为free_list的新节点        //注意参数nobjs是传引用        char *chunk=chunk_alloc(n,nobjs);        obj * volatile * my_free_list;        obj *result;        obj * current,*next_obj;        int i;        //如果只获得一个区块,这个区块就分配给调用者,free_list无新节点        if(1==nobjs)return chunk;        //否则调整free_list,纳入新节点        my_free_list=free_list+FREELIST_INDEX(n);        //以下在chunk空间内建立free_list        result = (obj *)chunk; //这一块准备返回给客户端        //以下引导free_list指向新配置的空间        *my_free_list=next_obj=(obj *)(chunk+n);        //以下将free_list的各节点串接起来        for(i=1;;++i){//从1开始,因为第0个将返回给客户端            current_obj=next_obj;            next_obj=(obj *)((char *)next_obj+n));            if(nobjs-1==i){                current_obj->free_list_link=0;                break;            }            else{                current_obj->free_list_link=next_obj;               }        }        return result;    }
  chunk_alloc函数  代码:
    template <bool threads,int inst>    //假设size已经上调至8的倍数    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;        }        else if(bytes_left>=size){            //内存池剩余空间不能完全满足需求量,但足够供应1个以上的区块            nobjs=bytes_left/size;            total_bytes=size*nobjs;            result=start_free;            start_free+=total_bytes;            return result;        }        else{            //内存池剩余空间连一个区块大小都无法提供            size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4);            //以下试着让内存池中的残余零头还有些价值            if(byte_left>0){                //内存池还有一些零头,先分配给适当的free_list                //首先寻找适当的free_list                obj * volatile * my_free_list=free_list+FREELIST_INDEX(n);                //调整free_list,将内存池中的残余空间编入                ((obj*)start_free)->free_list_link=my_free_list;                *my_free_list=(obj*)start_free;            }            //配置heap空间,用来补充内存池            start_free=(char*)malloc(byte_to_get);            if(0==start_free){                //heap空间不足,malloc失败                int i;                obj * volatile * my_free_list,*p;                //试着检视我们手上拥有的东西,这不会造成伤害,我们不打算尝试配置较小的区块,                //因为那在多进程中容易造成灾难,以下寻适当的free_list,所谓适当是指“尚有未用区块,且区块足够大”。                for(i=size;i<=__MAX_BYTES;i+=__ALIGN){                    my_free_list=free_list+FREELIST_INDEX(i);                    p=*my_free_list;                    if(p!=0){//尚有未用区块                        //调整free_list已释放出未用区块                        *my_free_list=p->free_list_link;                        start_free=(char *)p;                        end_free=start_free+i;                        //递归调整自己,修改nobjs;                        return chunk_alloc(size,nobjs);                        //注意,任何残余零头都要编入适当的free_list中备用;                    }                }                end_free=0;//如果出现意外(都没有内存用了)                //调用第一级配置器,看看out_of_memory机制能否尽力                start_free=(char *)malloc_alloc::allocate(bytes_to_get);                //这会导致抛出异常,或者内存不足情况将得到改善            }            heap_size+=bytes_to_get;            end_free=start_free+bytes_to_get;            //递归调用自己,修正nobjs;            return chunk_alloc(size,nobjs);        }    }

上述的chunk_alloc函数以end_free-start_free来判断内存池的水量,如果充足,就直接调出20个区块返回给free_list,如果水量不足以提供20个区块,但还足够提供至少一个区块,就拨出这不足20个区块的空间出去,这时候其pass by reference 的参数nobjs将修改为实际能提供的区块数,如果内存池连一个区块都无法提供,对客户显然无法交代,此时需要malloc从heap中配置内存,为内存池注入活水,新水量为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
如下图:
这里写图片描述

原创粉丝点击