STL空间配置器

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 destructor
template <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中配置内存,为内存池注入活水,新水量为需求量的两倍,再加上一个随着配置次数增加而愈来愈大的附加量。
如下图:
这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值