剖析——SGI版本下的空间配置器

来源:互联网 发布:g92内螺纹编程实例 编辑:程序博客网 时间:2024/05/17 04:14

1.为什么STL中会把空间配置器单独给出来呢?

编号 原因 解释 ① 解决代码冗余 每个容器中都写出来,会造成代码冗余 ② 用户自己申请容易内存泄漏 ③ 效率不高 malloc,会引发brk系统调用执行的过程 ④ 造成内存碎片 申请小块内存,造成内存片段间有很多碎片没有利用 ⑤ 额外负载 系统用来管理内存会额外开辟结构体

2.空间配置器使用接口

这里写图片描述
3.一二级空间配置器的实现

3.1一级空间配置器
简介:简单封装了malloc /free进行实现,并给出了set_new_handler 函数,在 malloc 失败时,调用句柄函数或选择抛出异常

#include<new>typedef void(*NewHandlerFunPtr)();template<int inst>class mallocAllocTemplate//一级空间配置器{private:    //以下函数用来处理内存不足的情况    static void* oom_malloc(size_t );    static void* oom_realloc(void* , size_t);    static NewHandlerFunPtr malloc_alloc_oom_hanlder;public:    static void* allocate(size_t n)    {        void * res = malloc(n);        if (res == NULL)        {            res = oom_malloc(n);        }        return res;    }    static void* reallocate(void* p,size_t new_size)    {        void * res = realloc(p,new_size);        if (res == NULL)        {            res = oom_realloc(p,new_size);        }        return res;    }    void deallocate(void* p)    {        free(p);//第一级空间配置器直接使用free()    }    //用户设置自己的out_of_memory handler    static NewHandlerFunPtr set_malloc_handler(NewHandlerFunPtr p)    {        NewHandlerFunPtr old = malloc_alloc_oom_hanlder;        mallocMllocMemplate = p;        return old;    }};//初始化处理函数指针template<int inst>NewHandlerFunPtr mallocAllocTemplate<inst>::malloc_alloc_oom_hanlder = 0;template<int inst>void* mallocAllocTemplate<inst>::oom_malloc(size_t n){    NewHandlerFunPtr my_alloc_handler;    void* res;    for (;;)//这样死循环效率高    {        my_alloc_handler = malloc_alloc_oom_handler;        if (0 == my_alloc_handler)        {             throw std::bad_alloc();        }        (*my_alloc_handler)();        res = malloc(n);        if (res)        {            return res;        }    }}template<int inst>void* mallocAllocTemplate<inst>::oom_realloc(void* p,size_t n){    NewHandlerFunPtr my_alloc_handler;    void* res;    for (;;)    {        my_alloc_handler = malloc_alloc_oom_handler;        if (0 == my_alloc_handler)        {            throw std::bad_alloc();        }        (*my_alloc_handler)();        res = realloc(p,n);        if (res)        {            return res;        }    }}

3.2二级空间配置器实现原理详解
维护了一个内存池和一张自由链表,哈希链实现快速定址。(头删 头插操作进行分配内存以及释放)
这里写图片描述

//二级空间配置器的实现//思路:如果申请的空间足够大(>128byte),移交第一级空间配置器//如果较小,使用二级空间配置器//实现以下几个函数enum{_ALIGN=8};enum{_MAXBYTES=128};//自由链表中最大块的大小是128enum{_NFREELISTS=16};//自由链表的长度,等于_MAXBYTES/_ALIGNtemplate<bool threads, int inst>class _DefaultAllocTemplate{    typedef mallocAllocTemplate<inst>  malloc_alloc;public:    union _Obj //自由链表的结点类型    {        _Obj* _freeListLink;//指向自由链表的指针        char _clientData[1];//用户可以使用的地址    };private:    static char* _startFree;//内存池的起始地址    static char* _endFree;//内存池的结束地址    static  size_t _heapSize;//记录内存池已经向系统申请了多大的内存    static _Obj* volatile _freeList[_NFREELISTS];//自由链表private:    static size_t _GetFreeListIndex(size_t bytes)// 获取这个字节在自由链表中的位置    {        return (bytes + (size_t)_ALIGN - 1) / (size_t)_ALIGN - 1;    }    static size_t _GetRoundUp(size_t bytes)// 对这个字节向上取整8的倍数    {        return (bytes + (size_t)_ALIGN - 1)&(~(_ALIGN-1));    }    //返回大小n的对象,并可能加入大小为n的其他区块到free list    static void* refill(size_t n);    //配置一大块空间,可容纳nobjs个大小为“size”的区块    //如果配置nobjs个区块有所不便,nobjs可能会降低    static char* chunk_alloc(size_t size, int& nobjs);public:    static void* allocate(size_t n);    static void dealloccate(void*p, size_t n);    static void* reallocate(void*p, size_t old_sz,size_t new_sz);};//static data member 的定义与初值设定template<bool threads, int inst>char* _DefaultAllocTemplate<threads, inst>::_startFree = 0;template<bool threads, int inst>char* _DefaultAllocTemplate<threads, inst>::_endFree = 0;template<bool threads, int inst>size_t _DefaultAllocTemplate<threads, inst>::_heapSize = 0;template<bool threads, int inst>typename _DefaultAllocTemplate<threads, inst>::__Objs*volatile_DefaultAllocTemplate<threads, inst>::_freeList[_NFREELISTS] = { 0 };template<bool threads, int inst>static void* _DefaultAllocTemplate<threads, inst>::allocate(size_t n)//分配空间{    void* ret;    //先判断要分配的空间是否大于128字节    if (n > _MAXBYTES)    {        ret=malloc_alloc::_Allocate(n);    }    //否则就取自由链表中找    _Obj*& myfreeList = _freeList + _GetFreeListIndex(n);    _Obj* result = myfreeList;    if (result == 0)//如果这个结点下面没有挂内存,则就要去内存池申请    {        ret =refill(_GetRoundUp(n));   //去内存池中申请    }    else    {        //头删        myfreeList = myfreeList->_freeListLink;        ret = result;    }    return ret;}template<bool threads, int inst>static void _DefaultAllocTemplate<threads, inst>::dealloccate(void*p, size_t n)//释放空间{    //先判断这个字节大小    if (n > _MAXBYTES)//如果大于128字节,调用一级空间配置器的释放函数    {        malloc_alloc::deallocate(p);    }    else   //将这段内存回收到自由链表中    {        //头插        _Obj* q=(_Obj*)p;        q=q->_freeListLink;    }}template<bool threads, int inst>static void* _DefaultAllocTemplate<threads, inst>::reallocate(void*p, size_t old_sz, size_t new_sz);//重新分配空间template<bool threads, int inst>void* _DefaultAllocTemplate<threads, inst>::refill(size_t n)//重新分配空间{    int nobjs = 20;    char* chunk=chunk_alloc(n,nobjs);//向内存池中一次索要20个,返回1-20                               //为了效率    if (nobjs == 1)    {        return chunk;    }    _Obj*ret = (_Obj*)chunk;//将申请的第一个对象作为返回值,将剩余的nobjs-1个挂接到自由链表中    _Obj* my_free_list = _free_list +_GetFreeListIndex(n);    my_free_list = (_Obj*)(chunk + n);    _Obj* cur = my_free_list;    _Obj* next = cur;    cur->_freeListLink = 0;    for (int i = 1; i < nobjs; i++)    {        next = (_Obj*)((char*)next + n);        cur->_freeListLink = next;        cur = next;    }    return ret;}//向系统申请内存template<bool threads, int inst>char* _DefaultAllocTemplate <threads, inst> ::chunk_alloc(size_t size, int& nobjs){    char* result = 0;    size_t totalBytes = size* nobjs;    size_t leftBytes = _endFree - _startFree;    if (leftBytes >=totalBytes)//如果剩余内存大于总请求内存    {        result = _startFree;        _startFree += totalBytes;        nobjs = 20;        return result;    }    else if (leftBytes > size)//如果剩余内存足够分=分配一个size    {        nobjs = (int)(leftBytes / size);        result = _startFree;        _startFree += (nobjs*size);        return result;    }    else//内存池中一个size也分配不出来    {        //内存池开辟新的容量        size_t NewBytes = 2 * totalBytes + _GetRoundUp(_heap.size>>4);        if (leftBytes > 0)//剩余的内存挂到自由链表上        {            _Obj*  myFreeList = _freeList + _GetFreeListIndex(leftBytes);            ((_Obj*)_startFree)->_freeListLink = myFreeList;            myFreeList = (_Obj*)_startFree;        }        //开辟新的内存        _startFree = (char*)malloc(NewBytes);        if (0 == _startFree)//如果开辟失败        {            //如果开辟失败,表明系统已经没有内存了,这时候,就要到自由链表中找一块比n            //还大的内存块,如果还没有的话,那就只能调用一级空间配置器            size_t index = _GetFreeListIndex(size);            for (; i < (size_t)_NFREELISTS; index++)            {                _Obj* p = _freeList + index;                if (!p)//自由链表中找到一块合适的内存                {                    //摘下来放到内存池                    _startFree = (char*)p;                    _endFree = _startFree + (index + 1) >> 3;                    //内存池中已经放入内存,可以进行切割分配了                    return chunk_alloc(size,nobjs);                }            }            //要是还找不到内存,只能交给一级空间配置器            _endFree = NULL;            _startFree = (char*)malloc_alloc::allocate(NewBytes);;        }        //开辟成功的,就更新heapSize(记录总共向系统申请了多少内存),更新_endFree        _heapSize += NewBytes;        _endFree = _startFree + NewBytes;        return chunk_alloc(size,nobjs);//现在内存池中可以去分配内存了    }}

空间配置器缺点:

1.有内碎片的问题

2.内存池在结束之前不能释放,被分割为小块的内存一直挂在自由链表下面,会使得内存的占用率过高。(因为这个时候你无法知道释放所需要的头部是哪一块)

解决方案:

①在二级空间配置器的内存池上面设置一个分配内存的上限,也就是当 _heapsize 到达一定的值时,程序就会自动的抛出异常,提醒用户二级空间配置器占用的内存过多了?

②实现一个机制,保存每次 malloc 出来的空间的首地址,并记录开辟的空间在自由链表中(已分为小块)挂的位置。当发现开辟的空间分成小块全都挂在自由链表下时,说明可以释放空间了,现将每个小块从自由链表中删除,然后 free 。