STL之空间配置器

    空间配置器隐藏在STL组件的背后,默默奉献,十分低调。整个STL的操作对象都存放到容器之内,而容器一定需要配置空间以置放数据,需要空间配置器分配内存。
    C++的内存配置的基本操作是 new 和 delete。这两个全局函数相当于C的 malloc 和 free 。SGI正是以这两个函数完成内存的配置与释放。    
    考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,第一级配置器直接使用 malloc 和 free ,第二级配置器则视情况采用不同的策略:当配置区块超过128字节时,便调用第一级配置器;当配置区块小于128字节时,为了降低额外负担,便采用复杂的内存池整理方式。整个设计究竟只开放第一级配置器,或是同时开放第二级配置器,取决于__USE_MALLOC 是否被定义:
# ifdef __USE_ALLOC
...
typedef __malloc_alloc_template<0> malloc_alloc;
typedef malloc_alloc alloc;    //一级配置器
# else
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;   //二级配置器
# endif
    
    alloc并不接受任何模板性别参数。无论alloc如何被定义,SGI还为它包装一个接口,使之符合STL规格:
template<class T, class Alloc>
class simple_alloc{
public:
    static T *allocate(size_t n)
        {return 0==n? (T*)Alloc::allocate(n*sizeof(T));}
    static T *alloc(void)
        {return (T*)Alloc::allocate(sizeof(T));}
    static T *deallocate(T *p, size_t n)
        {if(0 != n) Alloc::deallocate(p, n*sizeof(T));}
    static T *deallocate(T *p)
        {Alloc::deallocate(p, sizeof(T));}
};
    SGI STL容器全都是用这个simple_alloc接口。两级配置器的关系,接口包装以及实际运用方式:
//SGI STL第一级配置器
template<int inst>
class__malloc_alloc_template{};
//其中:
//1.allocate()直接使用malloc(),deallocate直接使用free()
//2.模拟C++的set_new_handler()以处理内存不足的状况

//SGI STL第二级配置器
template<bool threads, int inst>
class __default_alloc_template{};
//其中:
//1.维护16哥自由链表(free lists),负责16种小型区块的次配置能力。内存池以malloc配置而得;如果内存不足转调用第一级配置器。
//2.如果需求区块大于128字节,就转调用第一级配置器。

第一级配置器__malloc_alloc_template
template<int inst>
class __malloc_alloc_template{

private:
//oom : out of memory
static void *oom_malloc(size_t);
static void *oom_realloc(void*, size_t);
static void (*__malloc_alloc_oom_handler)();
 
public:
static void *allocate(size_t n){
    void *result = malloc(n);
    if(0 == result) result = oom_malloc(n);
    return result;
}
static void deallocate(void *p, size_t) {free(p);}
static void *reallocate(void *p, size_t ,size_t new_sz){
    void *result = realloc(p, new_sz);
    if(0 == result) result = oom_realloc(p, new_sz);
    return result;
}
static void (* set_malloc_handler(void (*f)())(){
    void (*old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return (old);
}
};
 
template<int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
 
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n){
    void (* my_malloc_handler)();
    void *result;
    for(;;){
        my_malloc_handler = __malloc_alloc_oom_handler;
        if(0 == my_malloc_handler){__THROW_BAD_ALLOC;}
        (*my_malloc_handler)();
        result = malloc(n);
        if(result) 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(0 == ) {__THROW_BAD_ALLOC;}
        (*my_malloc_handler)();
        result = realloc_(p, n);
        if(result) return result;
    }
}
 
typedef __malloc_alloc_template<0> malloc_alloc;

    第一级配置器以malloc、free、realloc等C函数执行实际的内存配置、释放、重配置操作,并实现出类似C++ new-handler的机制。它不能直接运用该机制,因为它不是使用 new来配置内存。
    SGI第一级配置器的 allocate和 realloc都是在调用 malloc和 realloc不成功后,该调用 oom_malloc和 oom_realloc。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用后,获得足够的内存。但如果该例程未被客端设定,便丢出 bad_alloc异常,或者直接exit(1)。

第二级配置器__default_alloc_template
    二级配置器多了一些机制,避免了太多小额区块造成内存的碎片;额外区块越小,额外负担所占的比例就愈大,浪费严重。    
    SGI第二级配置器的做法是,如果区块足够大,超过128字节时,就移交第一级配置器处理。当区块小于128字节时,则以内存池管理,又称为此层级配置:每次配置一大块内存,并维护对应之自由链表。下次若再有相同大小的内存需求,就直接从自由链表中拨出。如果客端释放小额区块,就由配置器回收到自由链表中。为方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数,并维护16个自由链表,各自管理8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128字节的小额区块。free-lists的节点结构如下:
union obj{
    union obj *free_list_link;
    char client_data[1];
};
    这是一个很提神的结构,从其第一个字段观之,obj可被视为一个指针,指向相同形式的另一个obj;从其第二个字段观之,obj可以被视为一个指针,指向实际区块。一物两用,非常节约!
    下面是而级配置器的部分实现:
enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};

//无模板性别参数,第二参数没派上用场
template <bool threads, int inst>
class __default_alloc_template{
private:
    static size_t ROUND_UP(size_t bytes){return (((bytes)+__ALIGN-1) & ~(__ALIGN-1));}
    union obj{
        union obj* free_list_link;
        char client_data[1];
    };
    static obj * volatile free_lists[__NFREELISTS];
    static size_t FREELIST_INDEX(size_t bytes){return((bytes+__ALIGN-1)/__ALIGN -1);}
    static void *refill(size_t n);
    static char *chunk_alloc(size_t size, int &nobjs);
    static char *start_free;
    static char *end_free;
    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_sz, size_t new_sz);
};
 
//some defination and initialization
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[__NFREELISTS] = 
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,};
    _default_alloc_template 拥有配置器的标准接口函数allocate ;此函数首先判断区块的大小,大于128bytes则调用第一级配置器,小于就检查对应的free list。如果free list有可用区块,拿来主义!若没有可用的区块,就将区块大小上调至8倍数边界,然后调用refill,准备为free list重新填充空间。
// n must be > 0
static void *allocate(size_t n){
    obj *volatile *my_free_list;
    obj *result;
    if(n>(size_t) __MAX_BYTES){return (malloc_alloc::allocate(n));}
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if(0 == result){return refill(ROUND_UP(n));}
    *my_free_list = result->free_list_link;
    return result;
}
    下面是deallocate函数,和allocate相似,首先判断区块大小,大于128bytes调用第一级配置器,否则找出对应的free list,将区块回收:
// p must not be 0
static void deallocate(void *p, size_t n){
    obj *q = (obj*) p;
    obj *volatile * my_free_list;
    if (n > (size_t) __MAX_BYTES){
        malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    q->free_list_link = *my_free_list;
    *my_free_list = q;
}
    重新填充free lists——refill
    allocate中,当它发现free list中没有可用区块时,就调用refill,准备为free list重新填充空间。新的空间将取自内存池。缺省取得20哥新区块,万一内存池空间不足,获得的节点数可能小于20:
template <bool threads, int inst>
void *__default_alloc_template<threads, inst>::refill(size_t n){
    int nobjs = 20;
    //调用 chunk_alloc,尝试取得nobjs哥区块作为free list的新节点,nobjs是pass by reference
    char *chunk = chunk_alloc(n, nobjs);
    obj *volatile *my_free_list;
    obj *result;
    obj *current_obj, *next_obj;
    int i;
    //只获得一个区块,就分配给调用者
    if(1 == obj) return chunk;
    my_free_list = free_list + FREELIST_INDEX(n);
    result = (obj*) chunk;
    *my_free_list = next_obj = (obj*)(chunk + n);
    for(i=1; ;++i){
        current_obj = next_obj;
        next_obj = (obj*)((char *)next)obj + n);
        if(nobjs -1 == i){ //从1开始,0将返回给客端
            current_obj->free_list_link = 0;
            break;
        }else{
            current_obj->free_list_link = next_obj;
        }
    return result;
    }
}
    内存池,提供mem给free list使用,其维护函数为chunk_alloc:
template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::
chunk_alloc(size_t size, int& nobjs){ // 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){ // 剩余空间至少能供应一个区块
        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(bytes_left > 0){
            //将零头配给合适的free list
            //寻找适当的free list
            obj *volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);
            //调整free list,将内存池中的残余空间编入
            ((obj*) start_free)->free_list_link = *my_free_list;
            *my_free_list = (obj*) start_free;
        }
        //配置 heap空间,补充内存池
        start_free = (char*) malloc(bytes_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(0 != p){
                    //调整free list 以释放出未用区块
                    *my_free_list = p->free_list_link;
                    start_free = (char* p);
                    end_free = start_free + i;
                    // 递归调用自己,为了修正nobjs
                    return(chunk_alloc(size, nobjs));
                }
            }
            end_free = 0;
            start_free = (char*)malloc_alloc::allocate(bytes_to_get);
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return(chunk_alloc(size, nobjs));
    }
}
    上述chunk_alloc函数以end_free - start_free来判断内存池的水量。如果水量充足,就直接调出20个区块返回给free list。如果水量不足以提供20个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去。nobjs参数将被修改为实际能够供应的区块数。如果内存池连一个区块都无法供应,便利用malloc从heap中配置内存,注入水量以满足需求。新水量的大小为需求量的两倍,再加上一个随配置量次数增加而愈来愈大的附加量。

    SGI容器通常以这种方式来使用配置器:
template<class T, class Alloc = alloc>
class vector{
public:
    typedef T value_type;
...
protected:
    typedef simple_alloc<value_type, Alloc> data_allocator;
...
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值