zoukankan      html  css  js  c++  java
  • 空间配置器

    1.为什么需要空间配置器

    1.1内存碎片

      从内存分配的角度来看,我们不免因为程序需求频繁申请、释放小块内存,从而在堆中造成外碎片,外碎片是指系统中空闲内存总量足够,但是不连续,所以无法分配给用户使用

    注:内碎片是指已经分配给用户,用户却不利用的内存。如用户需要3字节,实际却得到了4字节,其中的1字节是浪费掉的

    1.2频繁向系统申请小块空间,效率低

      内存空间是由操作系统管理的,当我们要去开辟时,调用malloc( ),要进行用户态/内核态的切换,这样系统调用会产生性能问题,频繁地因为很小一块内存就进行系统调用,效率很低

    2.空间配置器的实现

    2.1整体策略

      STL认为大于128字节的内存为大块内存,小于等于128字节的内存为小块内存,当请求的大块内存时,就使用第一层配置器分配内存,请求的小块内存则调用第二层配置器分配

    2.2一级空间配置器

    1)一级空间配置器的allocate()、deallocate()、reallocate()封装了malloc()、 free()、 realloc()等C库函数执行分配、释放、重新配置内存等操作

    2)此外,为了处理内存不足的状况,一级空间配置器增加handle处理机制:在内存请求无法被满足时,调用设定的函数

    3)一级空间配置器的allocate()如果调用malloc()分配内存不成功,将会调用内存不足处理函数oom_malloc(),oom_malloc()会循环调用错误处理函数__malloc_alloc_oom_handler,企图释放内存,再重新调用malloc(),直到分配成功;如果未设定错误处理函数,将直接bad_alloc异常信息

    template <int inst>
    class __malloc_alloc_template
    {
    private:
        static void *oom_malloc(size_t);   //使用malloc调用的内存不足处理函数
        static void *oom_realloc(void *, size_t); //使用realloc调用的内存不足处理函数
        static void(*__malloc_alloc_oom_handler)();     //错误处理函数
    public:
        static void * allocate(size_t n)                        {
            void *result = malloc(n);                     //一级空间配置器直接调用malloc
            if (0 == result)
                result = oom_malloc(n);
            return result;
        }
    
        static void deallocate(void *p, size_t /* n */)
        {
            free(p);
        }
    
        static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
        {
            void * result = realloc(p, new_sz);           //一级空间配置器直接调用realloc
            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>::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);
        }
    }

    2.3二级空间配置器

    1)二级空间配置器使用内存池+自由链表的机制:内存池负责向系统申请内存,分配小内存块给自由链表,自由链表共有16条,存储在数组free_list中,数组中由前往后的链表分别负责8字节,16字节,24字节,…,120字节,128字节的内存请求。为了便于管理,二级空间配置器在分配的时候字节数都是以8的倍数对齐,当内存请求字节数不是8的倍数时,将自动向上调整至8的倍数。当所需内存 小于等于128字节时,则去对应负责的自由链表上取内存块,当这块内存用完了,直接放回链表上即可回收;如果在申请内存时链表上没有内存块了,则向内存池申请内存块,此时会申请 nobjs 个(默认为20),将第1个返回给申请者,剩余的挂到对应链表上

    2)为了节省内存,将不使用额外的指针串连自由链表上的节点,而是采用一物两用的方法:节点是一个union,由于union的特性,_freeListLink指针可以指向下一个节点,_clientData又可以是内存块的首地址,也就是使用内存块提供的便利来存放下一个节点的地址,把节点串起来形成链表

    union _Obj                      //自由链表的节点
    {
        _Obj* _freeListLink;         //指向自由链表节点的指针
        char _clientData[1];          //内存块首地址
    };

    3)内存池分配内存块给自由链表的过程(函数_chunkAlloc):

    • 当内存池剩余的空间大小 leftBytes >= n x nobjs时,则直接分配好返回
    • 当内存池剩余的空间大小 leftBytes 的范围是 [ n x 1, n x nobjs ),则这时候就分配 nobjs = leftBytes / n 这么多块的内存块返回
    • 当内存池剩余的空间大小 leftBytes < n x 1 时,则先将剩余空间挂到自由链表上,再调用malloc()向系统申请 2 x n x nobjs + _GetRoundUp(_heapSize / 16) 字节的新内存。若申请成功,则再调用一次_chunkAlloc给自由链表分配内存块;若失败,则先去自由链表上找一块比 n 大的内存块,将它摘下来还给内存池,然后再调用一次_chunkAlloc分配内存,要是没找到,就调用一级空间配置器,看看内存不足处理机制能否处理

    enum { _ALIGN = 8 };              //按照基准值8的倍数进行内存操作
    enum { _MAXBYTES = 128 };        //自由链表中最大的块的大小是128
    enum { _NFREELISTS = 16 };       //自由链表数组的长度,等于_MAXBYTES/_ALIGN
    template <bool threads, int inst>  //非模板类型参数
    class _DefaultAllocTemplate
    {
        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向上取成8的倍数
        }
        static void* _Refill(size_t n);          //在自由链表中申请内存,n表示要的内存的大小
        static char* _chunkAlloc(size_t size, int& nobjs);    //在内存池中申请内存nobjs块,每个对象size个大小
    public:
        static void* Allocate(size_t n);      //n要大于0
        static void DeAllocate(void *p, size_t n);        //n要不等于0
    };
    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>::_Obj* volatile      //前面加typename表示后面是个类型
    _DefaultAllocTemplate<threads, inst>::_freeList[_NFREELISTS] = { 0 };    //自由链表
    
    //分配空间
    template<bool threads, int inst>
    void* _DefaultAllocTemplate<threads, inst>::Allocate(size_t n)    //n:要申请的字节数
    {
        void *ret;
        //大于_MAXBYTES(128)个字节则认为是大块内存,直接调用一级空间配置器
        if (n > _MAXBYTES)      
        {
            ret = malloc_alloc::_Allocate(n);
        }
        else       //否则就去自由链表中找
        {
            _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(n);  //让myFreeList指向自由链表中n向上取8的整数倍
            _Obj* result = *myFreeList;
            if (result == NULL)  //如果这条链表上没有挂内存块,则就要去内存池中申请
            {
                ret = _Refill(_GetRoundUp(n));      //到内存池中申请
            }
            else            //已经在自由链表上找到了内存块
            {
                *myFreeList = result->_freeListLink;      //把第2个内存块的地址放到自由链表上
                ret = result;    //第1个作为返回值
            }
        }
        return ret;
    }
    
    //回收空间
    template<bool threads, int inst>
    void _DefaultAllocTemplate<threads, inst>::DeAllocate(void *p, size_t n)    //n:要申请的字节数
    {
        //如果n大于128,就直接调用一级空间配置器的释放函数
        if (n > _MAXBYTES)  
        {
            malloc_alloc::_DeAllocate(p);
        }
        else //否则将这块内存回收到自由链表中
        {
            _Obj* q = (_Obj*)p;
            _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(n);
            q->_freeListLink = *myFreeList;
            *myFreeList = q;
        }
    }
    
    //自由链表向内存池取内存
    template<bool threads, int inst>
    void* _DefaultAllocTemplate<threads, inst>::_Refill(size_t n)    //n表示要申请的字节数
    {
        int nobjs = 20;           //默认向内存池一次性申请20块
        char* chunk = _chunkAlloc(n, nobjs);    //调用_chunkAlloc()向内存池申请,nobjs是引用传参,可以改变
        if (1 == nobjs)          //如果内存池只分配了1块,则直接返回给调用者
        {
            return chunk;
        }
        //如果分配了多块,则返回第1块给调用者,其他挂在自由链表上
        _Obj* ret = (_Obj*)chunk;    //将第1块作为返回值
        _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(n);
        *myFreeList = (_Obj*)(chunk + n);  //将第2块的地址放到自由链表上
        _Obj* cur = *myFreeList;
        _Obj* next = NULL;
        for (int i = 1; i < nobjs; ++i)        //将剩下的块挂到自由链表上
        {
            next = (_Obj*)((char*)cur + n);
            cur->_freeListLink = next;
            cur = next;
        }
        cur->_freeListLink = NULL;
        return ret;
    }
    
    //内存池向系统申请内存
    //size:小内存块的字节数,nobjs:要申请的块数
    template<bool threads, int inst>
    char* _DefaultAllocTemplate<threads, inst>::_chunkAlloc(size_t size, int& nobjs)  
    {
        char* result = NULL;
        size_t totalBytes = size * nobjs;        //所请求的内存大小
        size_t leftBytes = _endFree - _startFree;      //内存池剩余的大小
    
        if (leftBytes >= totalBytes)     //内存池剩余大小足够分配nobjs块
        {
            result = _startFree;
            _startFree += totalBytes;
            return result;
        }
        else if (leftBytes >= size)    //内存池剩余大小不够分配nobjs块,但至少够1块
        {
            nobjs = (int)(leftBytes / size);
            result = _startFree;
            _startFree += (nobjs*size);
            return result;
        }
        else    //内存池剩余大小连1块都不够分配了
        {
            if (leftBytes > 0)  //把内存池的零头挂到自由链表上
            {
                _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(leftBytes);
                ((_Obj*)_startFree)->_freeListLink = *myFreeList;
                *myFreeList = (_Obj*)_startFree;
            }
            //内存池调用malloc()开辟新内存,一次性开辟
            size_t NewBytes = 2 * totalBytes + _GetRoundUp(_heapSize >> 4);       
            _startFree = (char*)malloc(NewBytes);
            if (0 == _startFree)    //开辟失败
            {
                //开辟失败的话,首先去自由链表上找一块比n大的内存块
                for (size_t i = size; i < (size_t)_MAXBYTES; i += (size_t)_ALIGN)
                {
                    _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(i);
                    _Obj* p = *myFreeList;
                    if (NULL != p)       //在自由链表找到一块内存块
                    {
                        _startFree = (char*)p;
                        //将这个内存块摘下来还给内存池
                        *myFreeList = p->_freeListLink;
                        _endFree = _startFree + i;
                        return _chunkAlloc(size, nobjs);  //内存池开辟好的话,就再调一次chunk分配内存
                    }
                }
                //要是找不到的话,就调一级空间配置器,其中有内存不足处理机制
                _endFree = NULL;
                _startFree = (char*)malloc_alloc::_Allocate(NewBytes);
            }
            //开辟成功,就更新heapSize,更新_endFree
            _heapSize += NewBytes;
            _endFree = _startFree + NewBytes;
            return _chunkAlloc(size, nobjs);    //内存池开辟好的话,就再调一次chunk分配内存
        }
    }
    
    typedef _DefaultAllocTemplate<0, 0>  default_alloc;

    3.空间配置器存在的问题

    1)内碎片:容易产生内碎片,自由链表上所挂的内存块的大小都是8字节的整数倍,因此当我们需要非8倍数的内存块,往往会导致浪费,比如我只要1字节,但是自由链表最低分配8字节,也就浪费了7字节。这一点在计算机科学中很常见
    2)没有释放自由链表上所挂内存块的函数:空间配置器中所有的函数和变量都是 static 的,那么他们是存放在数据段的,又没有写释放他们的函数,所以在程序结束的时候才会释放他们,这样就导致自由链表一直占用着内存

  • 相关阅读:
    yarn之安装依赖包
    Yarn 的工作流-创建一个新项目
    yarn使用
    yarn安装
    用yarn替代npm
    搭建开发环境
    网页瞬间转换成桌面应用级程序(IOS/Win/Linux)
    [转]js模块化编程之彻底弄懂CommonJS和AMD/CMD!
    Node.js模块导出exports 和 module.exports 的区别
    Javascript modules--js 模块化
  • 原文地址:https://www.cnblogs.com/Joezzz/p/10300512.html
Copyright © 2011-2022 走看看