STL二级空间配置器

c/c++

浏览数:255

2019-3-28

STL的二级空间配置器类似于memory_pool,但是并不是memory_pool,因为STL的二级空间配置器只维护了16个free_list,而且只是小于128byte大小的小区块,大于128byte会调用一级空间配置器malloc。16个链表放在数组中,每一个数组所链的链表代表大小相等的小区块,因为分配的时候,只会申请8的倍数大小的空间,所以小于128byte的区块肯定是属于free_list里面。当小区块释放时,就会挂到free_list中。

下面是二级空间配置器的源码:

template <bool threads, int inst>class __default_alloc_template

_ALIGN = 8,表示最小的小区块的大小

_MAX_BYTES = 128 表示最大的区块的大小

_NFREELISTS = 16 表示free_list的个数

#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))
    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
# endif

这是将申请的大小按8字节对齐:

  static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

这是free_list的Node结构体:

__PRIVATE:
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };

free_list的定义:

# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    static _Obj* __STL_VOLATILE _S_free_list[]; 
        // Specifying a size results in duplicate def for 4.1
# else
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];  //volatile 是为了防止在多线程下产生不可预期的后果
# endif

内存池的相关信息:

 static char* _S_start_free; //内存池中未分给free_pool的首地址
 static char* _S_end_free; //内存池的末尾地址
 static size_t _S_heap_size;//内存池的总空间大小

二级空间配置器分配函数:

  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    //如果size大于128byte就使用一级配置器
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n); //找到size对应的list
      // Acquire the lock here with a constructor call.
      // This ensures that it is released in exit or during stack
      // unwinding.
#     ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;//有关线程安全
#     endif
      _Obj* __RESTRICT __result = *__my_free_list; //指向size所在链表头 
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));  //当这个链表为空时,重新创建一个小chunk
      else {
        *__my_free_list = __result -> _M_free_list_link; //将链第一个节点取下来
        __ret = __result; //给用户返回取下来的小区块的地址
      }
    }
    return __ret;
 };

下面的函数是从一级空间配置器(malloc)申请小块内存的函数:

template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
    int __nobjs = 20;
    /*默认的建议值,在申请小内存块的时候,一次申请的个数,这并不是最终个数,会根据内存池剩下的空间大小进行调整*/
    char* __chunk = _S_chunk_alloc(__n, __nobjs);//从内存池中申请_nobjs个_n大小的空间
    _Obj* __STL_VOLATILE* __my_free_list; //指向free_list的指针
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;

    /*如果在_S_chunk_alloc中只申请到了一块这样大的空间,则直接返回*/
    if (1 == __nobjs) return(__chunk);
    __my_free_list = _S_free_list + _S_freelist_index(__n); //找到该大小所对应的free_list

    /*将得到的多块_n大小的chunk,第一块返回给用户,剩下的加到_n所对应的free_list中*/
      __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 -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}

_S_chunk_alloc:

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);//调整需要申请的块数 //使用内存池剩余空间分配大小为_size的内存块,能分多少块就分多少块
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;  //将内存池的start往下移,分配空间
        return(__result);
    } else {
        size_t __bytes_to_get = 
      2 * __total_bytes + _S_round_up(_S_heap_size >> 4);  //内存吃没有足够的空间分配时,需要重新申请空间,这里的size就是需要向一级空间配置器申请的空间大小
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);//将内存池剩余的小于_size的空间挂到对应大小的free_list上。

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        /*内存池剩余为0时,malloc一个小块内存,进行分配*/
        _S_start_free = (char*)malloc(__bytes_to_get);
        if (0 == _S_start_free) {
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
        _Obj* __p;
        /*如果这是第一次申请内存池空间,则进行循环分配小块空间*/
            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));

                }
            }
        //将申请的空间分配完之后,再申请一块内存池空间作为备用
        _S_end_free = 0; 
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);

        }
        /*如果不是第一次申请内存池空间,把内存池的大小扩大,再调用自己进行空间分配*/
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;
        return(_S_chunk_alloc(__size, __nobjs));
    }
}

这就是整个STL库中的二级空间配置器的流程,其实也在模仿ptmalloc的内存管理机制,使用内存池和空间回收,避免多次库函数调用或者系统调用,大大节省了程序运行的时间。这里的二级空间配置器只要用到了free_list和小块内存池,双层保证小块空间分配的速率比直接malloc快。但是在实战中的效率对比还是需要实践才能得出结论。