空间配置器

空间配置器

allocator有标准的接口,SGI为了实现优化,并没有按照标准接口来实现。但是并不会造成大的影响。在实现中为容器配置了SGI的allocator。并且实现了标准的接口,以便用户使用。

具备次配置力(sub-allocation)的SGI空间配置器

SGI STL的配置器与众不同,也与标准规范不同,其名称是allor而非allocator,而且不接受任何参数。换句话说,如果你要在程序中明白采用SGI配置器,则不能采用标准写法:

vector<int, std::allocator<int> > iv;//VC or CB

vector<int, std::alloc> iv; //in GCC

SGI STL allocator未能符合标准规格,这个事实通常不会给我们带来困扰,通常我们使用缺省的空间配置器,很少需要自行指定配置器名称,而SGI STL每一个容器都已经指定其缺省的空间配置器为alloc。例如下面的vector声明:


template <class T, class Alloc = alloc>
class vector {//...
};

SGI特殊的空间配置器,std ::alloc

首先讨论我们平时使用的内存配置和释放的操作:

class Foo {...};
Foo* pf = new Foo;//配置内存, 构造内存
delete pf;//将对象析构,释放内存

new分为两段:

  1. 调用::operator new 配置内存;

  2. 调用Foo::Foo() 构造对象内容。

delete:

  1. 调用Foo::~Foo() 把对象析构。

  2. 调用::operator delete 释放内存。

为了精密分工,STL allocator决定将这两阶段操作区分开来。内存配置操作由alloc:allocate()负责,内存释放操作由alloc:deallocate()负责:对象构造操作由::construct()负责,对象析构操作由::destroy ( )负责.

#include <new.h>// 为了使用new place
template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
  new (__p) _T1(__value);//new place for assignment
}

template <class _T1>
inline void construct(_T1* __p) {
  new (__p) _T1();//default ctor
}

在这里需要注意语法 new(pointer) typename(value); 这是在已经分配了内存pointer的空间上进行初始化。place new

.

流程如上所示,destroy进行了特化版本,char不需要析构。在泛化版本中判断析构函数是否为trivial(翻译是无关疼痒 其实就是默认析构函数的意思)。这时候也是什么也不做。进行优化。

空间的配置与释放 std::alloc

其中设计中考虑的因素如下:

  • 向system heap要求空间。

  • 考虑多线程(multi-threads)状态。

  • 考虑内存不足时的应变措施。

  • 考虑过多“小型区块”可能造成的内存碎片(fragment)问题。

C++内存配置基本操作是::operator new ->和C中malloc()相当。SGI以malloc()和free完成内存的配置和释放。

配置的基本流程如下:

一级调度核心代码:


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:

  static void* allocate(size_t __n)
  {
    void* __result = malloc(__n);
    if (0 == __result) __result = _S_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);
    if (0 == __result) __result = _S_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>::_S_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) { __THROW_BAD_ALLOC; }//没有配置时 打印退出
     //#    define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
        (*__my_malloc_handler)();
        __result = realloc(__p, __n);
        if (__result) return(__result);
    }
}

第二级配置器__default_alloc_tem plate剖析

第二级配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的其实不仅是内存碎片,配置时的额外负担(overhead)也是一个大问题。

常规设计如上。

SGI第二级配置器的做法是,如果区块够大,超过128 bytes时,就移交第一级配置器处理。

当区块小于128 bytes时,则以内存池(memory pool)管理,此法又称为次层配置(sub-allocation ):每次配置一大块内存,并维护对应之自由链表(free-list )。下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客端释还小额区块,就由配置器回收到free-lists中—是的,别忘了,配置器除了负责配置,也负责回收。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客端要求30 bytes,就自动调整为32 bytes ),并维护16个free-fists,各自管理大小分别为8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88,96, 104, 1 l2, 120, 128 bytes的小额区块·free-lists的节点结构如下:

关注以上结构体为一个union类型,要么存放数据,要么指向下一个union结构。这样做能够节省空间。分配出去后不再获得。

关键函数分析

allocate

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

    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);//取得对应链表头
      // 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;
      if (__result == 0)//对应链表头为空
        __ret = _S_refill(_S_round_up(__n));//重新分配
      else {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;//取得结果
      }
    }

    return __ret;
  };

deallocate

 /* __p may not be 0 */
  static void deallocate(void* __p, size_t __n)
  {
    if (__n > (size_t) _MAX_BYTES)
      malloc_alloc::deallocate(__p, __n);
    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

在allocate发现free list没有可用的区块就会调用refill().新的空间将取自内存池(经由chunk_alloc()完成)。缺省取得20个新节点(新区块),但万一内存池空间不足,获得的节点数(区块数)可能小于20.


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);//需要分配的大小
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;

    if (1 == __nobjs) return(__chunk);//get return only to return 
    //more than one set to the list first of all, find the locate
    __my_free_list = _S_free_list + _S_freelist_index(__n);

    /* Build free list in chunk */
      __result = (_Obj*)__chunk;
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
      //insert into 插入到free list中
      for (__i = 1; ; __i++) {
        __current_obj = __next_obj;//because the pool is null now
        __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);
}
内存池

这个函数非常巧妙的递归调用,省去了重复的代码。

在最后必须到case 1或者case 2中才能真正的返回。

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) {//case 1完全分配
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else if (__bytes_left >= __size) {//case 2部分分配
        __nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
    } else {//case 3 不足以分配1个
        size_t __bytes_to_get = 
      2 * __total_bytes + _S_round_up(_S_heap_size >> 4);//(new size = old_size / 16) + 2*need add size 
        // Try to make use of the left-over piece. 
        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;
        }
      
        _S_start_free = (char*)malloc(__bytes_to_get);
      
        if (0 == _S_start_free) {//case 3.1 malloc失败
            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.
            //从比该free list 大的块中回收快 比如目前为8 则从16 24...回收一个再分配出去
            //使用递归_S_chunk_alloc 共享case 1 case 2代码
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;
                 __i += (size_t) _ALIGN) {
                    //try to free the free_list which bigger than _size
                __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));//guan
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
        _S_end_free = 0;    // In case of exception.
          //case 3.2 遍历完任然没有空闲的块 只能尝试一级调度里面释放内存重新分配的方式
            _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));
    }
}

以上为使用visio画的大致流程图。最终分配成功。

内存基本处理工具

举例说明, 以下以uninitialized_copy()为例子。


template <class _InputIter, class _ForwardIter>
inline _ForwardIter
  uninitialized_copy(_InputIter __first, _InputIter __last,
                     _ForwardIter __result)
{
  return __uninitialized_copy(__first, __last, __result,
                              __VALUE_TYPE(__result));
}
template <class _InputIter, class _ForwardIter, class _Tp>
inline _ForwardIter
__uninitialized_copy(_InputIter __first, _InputIter __last,
                     _ForwardIter __result, _Tp*)
{
  typedef typename __type_traits<_Tp>::is_POD_type _Is_POD;//是否为POD类型 
  return __uninitialized_copy_aux(__first, __last, __result, _Is_POD());
}

template <class _InputIter, class _ForwardIter>
inline _ForwardIter 
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                         _ForwardIter __result,
                         __true_type)//关注这个参数 默认构造器 所以直接使用内存拷贝
{
  return copy(__first, __last, __result);
}

template <class _InputIter, class _ForwardIter>
_ForwardIter 
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                         _ForwardIter __result,
                         __false_type)//使用构造函数
{
  _ForwardIter __cur = __result;
  __STL_TRY {
    for ( ; __first != __last; ++__first, ++__cur)
      construct(&*__cur, *__first);
    return __cur;
  }
  __STL_UNWIND(destroy(__result, __cur));
}

//以下为上面相关宏的说明 
#define __VALUE_TYPE(__i)        __value_type(__i)//类型转换
template <class _Iter>
inline typename iterator_traits<_Iter>::value_type*
__value_type(const _Iter&)
{
  return static_cast<typename iterator_traits<_Iter>::value_type*>(0);
}
#   define __STL_TRY try
#   define __STL_UNWIND(action) catch(...) { action; throw; }
//以下为特化函数
inline char* uninitialized_copy(const char* __first, const char* __last,
                                char* __result) {
  memmove(__result, __first, __last - __first);
  return __result + (__last - __first);
}

inline wchar_t* 
uninitialized_copy(const wchar_t* __first, const wchar_t* __last,
                   wchar_t* __result)
{
  memmove(__result, __first, sizeof(wchar_t) * (__last - __first));
  return __result + (__last - __first);
}

POD Plain Old Data 也就是标量型别(scalar types)或传统的C struct型别。POD型别必然拥有trivial ctor/dtor/copy/assignment函数,因此,我们可以对POD型别采用最有效率的初值填写手法,而对non-POD型.别采取最保险安全的做法.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值