zoukankan      html  css  js  c++  java
  • 具有次配置力的 SGI 空间配置器

    SGI STL 的配置器与众不同,也与标准规范不同,其名称是 a1loc 而非 allocator,而且不接受任何参数。

    第一级配置器

    • 以 malloc(),free(), realloc() 等 C 函数执行实际的内存配置、释放、重配置操作。
    • 实现出类似 C++ new-handler 的机制。所谓 C++ new handler 机制是,你可以要求系统在内存配置需求无法被满足时,调用一个你所指定的函数。
    • 第一级配置器内存分配失败一般是由于内存不足 out-of-memory(oom),处理异常时,首先用户自己设计异常处理例程,若用户没有定义,仅仅是打印错误信息并强制退出。
    • allocate() 和 realloc() 都是在调用 malloc() 和 realloc() 不成功后,改调用 oom_malloc() 和 oom_realloc()。后两者都有内循环,不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务。

    __malloc_alloc_template 定义

    template <int __inst>
    class __malloc_alloc_template {
    
    private:
      
      //@ 以下函数将用来处理内存不足的情况
      static void* _S_oom_malloc(size_t);
      static void* _S_oom_realloc(void*, size_t);
    
    #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
      static void (* __malloc_alloc_oom_handler)();
    #endif
    
    public:
    
      //@ 第一级配置器直接调用 malloc()
      static void* allocate(size_t __n)
      {
        void* __result = malloc(__n);
        // 以下无法满足需求时,改用 _S_oom_malloc()
        if (0 == __result) __result = _S_oom_malloc(__n);
        return __result;
      }
    
      //@ 第一级配置器直接调用 free()
      static void deallocate(void* __p, size_t /* __n */)
      {
        free(__p);
      }
      
      //@ 第一级配置器直接调用 realloc()
      static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
      {
        void* __result = realloc(__p, __new_sz);
        //@ 以下无法满足需求时,改用 _S_oom_realloc()
        if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
        return __result;
      }
    
      //@ 以下仿照 C++ 的 set_new_handler(),可以通过它指定自己的 out-of-memory handler
      //@ 为什么不使用 C++ new-handler 机制,因为第一级配置器并没有 ::operator new 来配置内存
      static void (* __set_malloc_handler(void (*__f)()))()
      {
        void (* __old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = __f;
        return(__old);
      }
    };
    
    // malloc_alloc out-of-memory handling
    #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
    //@ 初值为0,由客户自行设定
    template <int __inst>
    void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
    #endif
    
    template <int __inst>
    void*
    __malloc_alloc_template<__inst>::_S_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>::_S_oom_realloc(void* __p, size_t __n)
    {
        void (* __my_malloc_handler)();
        void* __result;
    
        //@  给一个已经分配了地址的指针重新分配空间,参数 __p 为原有的空间地址,__n 是重新申请的地址长度
        for (;;) {
    	//@ 当 "内存不足处理例程" 并未被客户设定,便调用 __THROW_BAD_ALLOC,丢出 bad_alloc 异常信息
            __my_malloc_handler = __malloc_alloc_oom_handler;
            if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
            (*__my_malloc_handler)();   //@ 调用处理例程,企图释放内存
            __result = realloc(__p, __n);  //@ 再次尝试配置内存,扩大内存大小
            if (__result) return(__result);
        }
    }
    
    //@ 直接将参数 __inst 指定为0
    typedef __malloc_alloc_template<0> malloc_alloc;
    

    第二级配置器

    • 如果区块足够大,超过 128 bytes,就交给第一级配置器处理。
    • 当区块小于 128 bytes,则使用 memory bool 管理,这种方法又称为 sub-allocation:
      • 每次配置一大块内存,并维护 free-list。
      • 下一次若有相同大小的内存需求,则直接从 free-list 中拨出。
      • 如果客户端释还小区块,就由配置器回收到 free-list 中。
      • 为了方便管理,SGI 第二级配置器会主动将任何小的区块的内存需求量上调到 8 的倍数,例如客户端需要 30 bytes,就会自动调整到 32 bytes。第二级配置器维护了16个 free-list,各自管理大小分别为:8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes 大小的区块。

    free -list 的结构:

    //@ free-list 的节点结构,降低维护链表 list 带来的额外负担
    union _Obj {
        union _Obj* _M_free_list_link;  //@ 指向相同形式的另一个 _Obj 
        char _M_client_data[1];    //@ 指向实际的区块
    };
    

    小区块常量:

    enum {_ALIGN = 8};  //@ 小区块的上调边界
    enum {_MAX_BYTES = 128}; //@ 小区块的上限
    enum {_NFREELISTS = 16}; //@ _MAX_BYTES/_ALIGN  free-list 的个数
    

    __default_alloc_template 定义

    //@ 无 “template 类型参数”,且第二参数也没有用,其中第一参数用于多线程环境下
    template <bool threads, int inst>
    class __default_alloc_template {
    ...
    //@ 将任何小额区块的内存需求量上调至 8 的倍数
    static size_t
    _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
    ...
    
    //@ 维护 16 个空闲链表(free list),初始化为0,即每个链表中都没有空闲数据块
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
    
    //@ 根据申请数据块大小找到相应空闲链表的下标,n 从 0 起算
    static  size_t _S_freelist_index(size_t __bytes) {
    return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
    }
    
    //@ 传回一个大小为 n 的对象,并可能加入到大小为 n 的其它区块到 free-list
    static void* _S_refill(size_t __n);
    
    //@ 配置一个大块空间,可容纳 __nobjs 个大小为 __size 的区块
    //@ 如果配置 __nobjs 个区块有所不便,__nobjs 会降低
    static char* _S_chunk_alloc(size_t __size, int& __nobjs);
    
    // Chunk allocation state.
    static char* _S_start_free;  //@ 内存池起始位置。只在 _S_chunk_alloc() 中变化
    static char* _S_end_free;    //@ 内存池结束位置。只在 _S_chunk_alloc() 中变化
    static size_t _S_heap_size;  //@ 内存池大小
    };
    

    空间配置函数 allocate()

    空间配置函数 allocate() 的具体实现步骤如下:

    • 若用户申请的内存大于 128bytes,则调用第一级配置器分配空间。
    • 若小于 128bytes 检查对应的自由链表 free_list,如果自由链表存在可用的区块,则直接使用,若不存在,则调用填充函数 refill() 为自由链表重新填充空间。
     //@ 申请大小为n的数据块,返回该数据块的起始地址 
      static void* allocate(size_t __n)
      {
        void* __ret = 0;
    
        //@ 如果需求区块大于 128 bytes,就转调用第一级配置
        if (__n > (size_t) _MAX_BYTES) {
          __ret = malloc_alloc::allocate(__n);	//@ 第一级配置器
        }
        else {
          //@ 根据申请空间的大小寻找相应的空闲链表(16个空闲链表中的一个)
          _Obj* __STL_VOLATILE* __my_free_list
              = _S_free_list + _S_freelist_index(__n);   
    #     ifndef _NOTHREADS
          /*REFERENCED*/
          _Lock __lock_instance;
    #     endif
          _Obj* __RESTRICT __result = *__my_free_list;
          //@ 空闲链表没有可用数据块,就将区块大小先调整至 8 倍数边界,然后调用 _S_refill() 重新填充
          if (__result == 0)
            __ret = _S_refill(_S_round_up(__n));
          else {
            //@ 如果空闲链表中有空闲数据块,则取出一个,并把空闲链表的指针指向下一个数据块  
            *__my_free_list = __result -> _M_free_list_link;
            __ret = __result;
          }
        }
    
        return __ret;
      };
    

    空间释放函数 deallocate()

    首先判断区块的大小,大于 128bytes 直接调用第一级配置器,若小于 128bytes,则找出相应的自由链表free_list,将其回收。

     //@ 空间释放函数 deallocate()
      static void deallocate(void* __p, size_t __n)
      {
        if (__n > (size_t) _MAX_BYTES)   
          malloc_alloc::deallocate(__p, __n);   //@ 大于 128 bytes,就调用第一级配置器的释放
        else {
        //@ 否则将空间回收到相应空闲链表(由释放块的大小决定)中  
          _Obj* __STL_VOLATILE*  __my_free_list
              = _S_free_list + _S_freelist_index(__n);   
          _Obj* __q = (_Obj*)__p;
    
          // acquire lock
    #       ifndef _NOTHREADS
          /*REFERENCED*/
          _Lock __lock_instance;
    #       endif /* _NOTHREADS */
          __q -> _M_free_list_link = *__my_free_list;   //@ 调整空闲链表,回收数据块
          *__my_free_list = __q;
          // lock is released here
        }
      }
    

    重新填充函数 refill()

    重新填充函数 refill() 是在自由链表不存在可用的区块时被调用。

    默认是为自由链表申请20个节点,第1个返回给客户端,剩下19个留给自由链表管理。

    template <bool __threads, int __inst>
    void*
    __default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
    {
        int __nobjs = 20;
        //@ 调用 _S_chunk_alloc(),缺省取 20 个区块作为 free list 的新节点
        //@ __nobjs 是引用传参
        char* __chunk = _S_chunk_alloc(__n, __nobjs);
        _Obj* __STL_VOLATILE* __my_free_list;
        _Obj* __result;
        _Obj* __current_obj;
        _Obj* __next_obj;
        int __i;
    
        //@ 如果只获得一个数据块,那么这个数据块就直接分给调用者,空闲链表中不会增加新节点
        if (1 == __nobjs) return(__chunk);
        
        //@ 否则根据申请数据块的大小找到相应空闲链表  
        __my_free_list = _S_free_list + _S_freelist_index(__n);  
    
        /* Build free list in chunk */
          __result = (_Obj*)__chunk;
          //@ 第0个数据块给调用者,地址访问即 chunk~chunk + n - 1  
          *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
          //@ 将free-list 的各个节点串联起来
          //@ 从1 开始,因为第0个传回给客户端
          for (__i = 1; ; __i++) {
            __current_obj = __next_obj;
            __next_obj = (_Obj*)((char*)__next_obj + __n);
            if (__nobjs - 1 == __i) {
                __current_obj -> _M_free_list_link = 0;
                break;
            } else {
                __current_obj -> _M_free_list_link = __next_obj;
            }
          }
        return(__result);
    }
    

    内存池管理 chunk_alloc()

    chunk_alloc 函数具体实现步骤如下:

    • 内存池剩余空间完全满足20个区块的需求量,则直接获取对应大小的空间。
    • 内存池剩余空间不能完全满足20个区块的需求量,但是足够供应一个及以上的区块,则获取满足条件的区块个数的空间。
    • 内存池剩余空间不能满足一个区块的大小,则:
      • 首先判断内存池中是否有残余零头内存空间,如果有则进行回收,将其编入free_list。
      • 然后向 heap 申请空间,补充内存池。heap 有足够的空间,空间分配成功。heap空间不足,即 malloc() 调用失败。则
        • 查找 free_list 中尚有未用区块,调整以进行释放,将其编入内存池。然后递归调用 chunk_alloc 函数从内存池取空间供 free_list 备用。
        • 搜寻 free_list 释放空间也未能解决问题,这时候调用第一级配置器,利用 out-of-memory 机制尝试解决内存不足问题。
    template <bool __threads, int __inst>
    char*
    __default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                                int& __nobjs)
    {
        char* __result;
        size_t __total_bytes = __size * __nobjs;  //@ 需要申请空间的大小 
        size_t __bytes_left = _S_end_free - _S_start_free;  //@ 计算内存池剩余空间
    
        if (__bytes_left >= __total_bytes) //@ 内存池剩余空间完全满足申请,直接分配
        {  
            __result = _S_start_free;
            _S_start_free += __total_bytes;
            return(__result);
        } else if (__bytes_left >= __size) //@ 内存池剩余空间不能满足申请,提供一个以上的区块
        {  
            __nobjs = (int)(__bytes_left/__size);
            __total_bytes = __size * __nobjs;
            __result = _S_start_free;
            _S_start_free += __total_bytes;
            return(__result);
        } else 	//@ 内存池剩余空间连一个区块的大小都无法提供    
        {                                               
            size_t __bytes_to_get = 
    	    2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
            //@ 尝试让 memory pool 中的残余空间利用起来 
    	   //@ 内存池的剩余空间分给合适的空闲链表
            if (__bytes_left > 0) {
                _Obj* __STL_VOLATILE* __my_free_list =
                            _S_free_list + _S_freelist_index(__bytes_left);
    
                ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
                *__my_free_list = (_Obj*)_S_start_free;
            }
            
            //@ 配置 heap 空间,用来补充内存池
            _S_start_free = (char*)malloc(__bytes_to_get);  
    		//@ heap 空间不足,malloc() 失败
            if (0 == _S_start_free) {//@ 若堆空间不足
                size_t __i;
                _Obj* __STL_VOLATILE* __my_free_list;
    	    _Obj* __p;
                // Try to make do with what we have.  That can't
                // hurt.  We do not try smaller requests, since that tends
                // to result in disaster on multi-process machines.
                for (__i = __size;
                     __i <= (size_t) _MAX_BYTES;
                     __i += (size_t) _ALIGN) {
                    __my_free_list = _S_free_list + _S_freelist_index(__i);
                    __p = *__my_free_list;
                    if (0 != __p) {
                        *__my_free_list = __p -> _M_free_list_link;
                        _S_start_free = (char*)__p;
                        _S_end_free = _S_start_free + __i;
                        return(_S_chunk_alloc(__size, __nobjs));
                        // Any leftover piece will eventually make it to the
                        // right free list.
                    }
                }
    	    _S_end_free = 0;	// In case of exception.
           _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);  //@ 调用第一级配置器
                // This should either throw an
                // exception or remedy the situation.  Thus we assume it succeeded.
            }
            _S_heap_size += __bytes_to_get;
            _S_end_free = _S_start_free + __bytes_to_get;
            return(_S_chunk_alloc(__size, __nobjs));  //@ 递归调用自己
        }
    }
    

    总结

    • 第一级配置器:__malloc_alloc_template,第二级配置器:__default_alloc_template。GCC 默认使用第二级配置器,其作用是避免太多小额区块造成内存的碎片
    • 第一级配置器直接使用 malloc()、free()、realloc() 等 C 函数,实现出类似 C++ new-handler 的机制。
    • 第二级配置器根据实际情况采取不同的策略:
      • 当配置区块超过 128 bytes,视为足够大,呼叫第一级配置器。
      • 当配置区块小于 128 bytes,视为比较小,则不求助第一级配置器。
    • 空间配置:allocate,空间释放:deallocate。
    • 重新装填:refill,内存池管理:chunk_alloc。
  • 相关阅读:
    LeetCode题解——两数之和
    题解LeetCode——回文数
    汇编语言入门教程
    python基础--局部变量与全局变量
    linux--基础知识1
    python基础--函数
    字符串format函数使用
    字符串的拼接
    python基础--6 集合
    python基础--5字典
  • 原文地址:https://www.cnblogs.com/xiaojianliu/p/12594498.html
Copyright © 2011-2022 走看看