C++STL中Allocator分析

C++STL中Allocator分析

C++ SGI STL的allocator分为两级:

  • 第一级配置器:超过128bytes的内存分配通过第一级分配器进行分配。

  • 第二级配置器:不超过128bytes的内存分配通过第二级分配器进行分配。

为了简单起见,文本在讨论的时候无考虑多线程的场景以及模板化。

第一级配置器

第一级配置器的实现比较简单,直接通过malloc请求内存,通过free释放内存。若通过malloc请求内存失败,则先尝试通过调用用户注册的处理函数尝试释放不再使用的内存,之后再次使用malloc进行请求内存。

具体的代码如下:

class __malloc_alloc{
private:
    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);
    static void deallocate(void *p, size_t n);
    static void *reallocate(void *p, size_t old_sz, size_t new_sz);
    static void (* set_malloc_handler(void (*f)()))(){
        void (*old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return old;
    }

};

void (*__malloc_alloc::__malloc_alloc_oom_handler)() = 0;

void *__malloc_alloc::oom_malloc(size_t n){
    void (*my_malloc_handler)();
    void *result;
    for(;;){
        my_malloc_handler = __malloc_alloc_oom_handler;             
        if(0 == my_malloc_handler) {
            std::cerr<<"out of memory"<<std::endl;      //若没有注册回调函数,抛出异常
            exit(0);
        }
        (*my_malloc_handler)();     //回调用户注册的内存释放函数,以寻求释放空闲内存
        result = malloc(n);
        if(result) return result;
    }
}

void *__malloc_alloc::oom_realloc(void *p, size_t n){
    void (*my_malloc_handler)();
    void *result;
    for(;;){
        my_malloc_handler = __malloc_alloc_oom_handler;
        if(0 == my_malloc_handler) {
            std::cerr<<"out of memory"<<std::endl;
            exit(0);
        }
        (*my_malloc_handler)();
        result = realloc(p, n);
        if(result) return result;
    }
}

void *__malloc_alloc::allocate(size_t n){
    void *result = malloc(n);                   //第一级适配器直接使用malloc请求内存
    if(0 == result) result = oom_malloc(n);     //若malloc无法请求内存,则先调用用户注册的回调函数尝试释放内存
    return result;
}

void __malloc_alloc::deallocate(void *p, size_t n){
    free(p);                                    //第一级适配器直接使用free释放内存
}

void *__malloc_alloc::reallocate(void *p, size_t old_sz, size_t new_sz){
    void *result = realloc(p, new_sz);
    if(0 == result) result = oom_realloc(p, new_sz);
    return result;
}

第二级配置器

第二级配置多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的起始不仅是内存碎片,配置时的额外负担也是一个大问题。SGI第二级配置器的做法是,如果区块够大,超过128b时,就移交第一级配置器处理。当区块小于128b时,则以内存池管理,此法又称为次层配置:每次配置一大块内存,并维护对应之自由链表。下次若再有相同大小的内存需求,就直接从free_list中拿出。如果客户端还释还小额区块,就由配置器回收到free_list中。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客户端需求为30b,就自动调整为32b)。

其维护两个数据结构:

空闲链表:16个free_lists,各种管理大小为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128btyes的小额区块。

C++STL中Allocator分析_第1张图片
C++STL中Allocator分析_第2张图片
C++STL中Allocator分析_第3张图片

内存池:在堆上分配的内存空间,free_start为可用空间起点,free_end为可用空间终点。

enum {__ALIGN = 8};             //小型区块的上调边界
enum {__MAX_BYTES = 128};       //小型区块的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};  //free-lists个数

class __default_alloc{
private:
    static size_t ROUND_UP(size_t bytes){       //将bytes上调至8的倍数
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
    }
    union obj{
        union obj * free_list_link;
        char client_data[1];
    };
    static obj * volatile free_list[__NFREELISTS];
    static size_t FREELIST_INDEX(size_t bytes){
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }
    static void *refill(size_t n);          //填充大小n的空闲链表
    static char *chunk_alloc(size_t size, int &nobjs);  //从内存池中分配size*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);
};

void * __default_alloc::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(result==0){         //空闲链表中无空闲空间
        void *r = refill(ROUND_UP(n));  //填充空闲链表
        return r;
    }
    *my_free_list = result->free_list_link;
    return result;
}

void  __default_alloc::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;
    return;
}

void *__default_alloc::refill(size_t n){
    int nobjs = 20;
    char *chunk = chunk_alloc(n, nobjs);    //尝试请求n*nobjs空间,实际得到的数量为nobjs
    obj *volatile * my_free_list;
    obj *result;
    obj* current_obj, *next_obj;
    int i;

    //如果只获得一个区块,这个区块就分配给调用者使用,free_list无新结点
    if(1==nobjs) 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){
            current_obj->free_list_link = 0;
            break;
        }
        else{
            current_obj->free_list_link = next_obj;
        }
    }   
    return result; 
}

char * __default_alloc::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){      //内存池剩余空间不能完全满足需求量,但足够供应一个以上的区块
        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;
            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); //配置heap空间,用来补充内存池
        if(0==start_free){  //heap空间不足,malloc失败
            int i;
            obj *volatile *my_free_list, *p;
            //检查当前free_list,看是否有比size大的空闲的空间,并对其进行重新配置
            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;
                    return chunk_alloc(size, nobjs); //递归调用自己,修正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;
        return chunk_alloc(size, nobjs); //递归调用自己,为了修正nobjs
    }
}

你可能感兴趣的:(C++,c++,开发语言,后端)