1空间配置器(allocator)

本文介绍STL中的内存管理机制,包括空间配置器allocator的工作原理、构造与析构工具stl_construct.h、内存空间配置与释放工具stl_alloc.h以及内存基本处理工具stl_uninitialized.h。深入探讨了不同配置器如何协作工作以及如何高效处理内存数据。

1、空间配置器(allocator)

STL将空间配置器定义于<memory>之中,<memory>内包含两个文件<stl_alloc.h>和<stl_construct.h>,一个负责内存空间的配置和释放另一个负责对象内容的构造与析构。

  • <stl_construct.h>:这里定义了全局函数construct()和destroy()负责对象的构造和析构
  • <stl_alloc.h>:这里定义了一、二级配置器,彼此合作负责内存空间的配置和释放
  • <stl_uninitialized.h>:这里定义了一些全局函数,用来填充(fill)或复制(copy)大块内存数据,包括un_initialized_copy()、un_initialized_fill()和un_initialized_fill_n(),这些函数虽不属于配置器的范畴,但与对象初值设置有关。对于容器的大规模元素初值设置很有帮助。这些函数对于效率都有面面俱到的考虑,最差的情况下会调用construct(),最佳情况会使用memmove()直接进行内存数据的移动。

2、构造和析构基本工具stl_construct.h

construct()和destroy()作为构造、析构之用的函数被设计为全局函数。

construct()接受一个指针p和一个初值value,该函数的用途就是将初值设定到指针所指的空间上,new云算子用来完成这一任务。

destroy()有两个版本,第一个版本接受一个指针,准备将该指针所指之物析构掉,直接调用该对象的析构函数即可。第二个版本接受first和last两个迭代器,准备将[first,last)范围内的所有对象析构掉。但是如果每个对象的析构函数都无关痛痒(所谓trivial destructor),那么一次次调用这些无关痛痒的析构函数对效率伤害很大,所以首先通过value_type()获得迭代器所指对象的类型,再利用_type_traits<T>判断该类型析构函数是否是trivial destructor,若是__true_type则什么也不做就结束,否则__false_type才循环遍历对每一个对象调用destroy()。

template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

template <class ForwardIterator>
inline void
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
  for ( ; first < last; ++first)
    destroy(&*first);
}

template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
  typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
  __destroy_aux(first, last, trivial_destructor());
}

template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
  __destroy(first, last, value_type(first));
}

inline void destroy(char*, char*) {}
inline void destroy(wchar_t*, wchar_t*) {}

3、内存空间的配置与释放stl_alloc.h

stl_alloc空间配置器设计哲学:

  • 向system heap要求空间
  • 考虑多线程状态
  • 考虑内存不足时的应变措施
  • 考虑过多小型区块可能造成的内存碎片问题

SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器视情况采用不同的策略:当配置区块超过128bytes时,视之为足够大,调用第一级配置器__malloc_alloc_template;当配置区块小于128bytes时,视之为过小,调用第二级配置器__default_alloc_template。

(1)第一级配置器__malloc_alloc_template

第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作,并实现出类似C++ new-handler的机制以处理内存不足的状况。

template <int inst>
class __malloc_alloc_template {

private:

static void *oom_malloc(size_t);

static void *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 = 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 = 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);
}

};

// malloc_alloc out-of-memory handling

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
#endif

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);
    }
}

template <int inst>
void * __malloc_alloc_template<inst>::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; }
        (*my_malloc_handler)();
        result = realloc(p, n);
        if (result) return(result);
    }
}

typedef __malloc_alloc_template<0> malloc_alloc;

(2)第二级配置器__default_alloc_template

SGI第二级配置器的做法是,如果要求的区块足够大,超过128bytes时,就移交第一级配置器处理;当要求的区块小于128bytes时,则以内存池管理,内存池管理就是每次配置一大块内存,这个一大块内存大小为nobjs*size,size是请求内存上调至8的倍数的大小,并维护对应之自由链表(free-list),如当前请求的是free_list[5]48bytes内存则free_list[5]下多了19个48bytes自由链表空间另外1个返回给了客端,下次若再有相同大小的内存需求,就直接从free-list中拔出。SGI第二级配置器会主动将请求的内存大小提升至8的倍数并维护16个自由链表free_list[16],各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块。

内存池管理原理:内存池大小[start_free,end_free),请求大小nobjs*size

  • 当内存池剩余空间完全满足需求量,就直接调出20个区块(nobjs*size)返回给free list
  • 当内存池剩余空间不能完全满足需求量,但足够供应一个以上的区块,就将这不足20个区块的空间拨出去,nobjs值修改
  • 当内存池剩余空间连一个区块的大小都无法提供:(i)先将内存池内还有的一些零头配给适当的free list,比如请求的是48bytes剩余45bytes就将40bytes分给free_list[4]浪费了5bytes空间 (ii)然后利用malloc从heap中配置内存2*nobjs*size+附加量heap_size<<4,为内存池注入源头活水以应付需求。如果heap空间不足malloc()失败,就将size大小之后的自由链表free list中找出一个空闲区块作为新内存池大小,递归调用内存池管理函数重新为请求分配区块,但如果size大小之后的自由链表free list都为空,只好调用第一级配置器,看看out-of-memory机制能否尽力。如果heap空间充足给内存池提供请求空间,递归调用内存池管理函数重新为请求分配区块,并且heap_size+=2*nobjs*size,这个heap_size/16作为下次内存池向heap请求空间的附加量。
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    if (1 == nobjs) return(chunk);
    my_free_list = free_list + FREELIST_INDEX(n);

    /* Build free list in chunk */
      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 -> free_list_link = 0;
            break;
        } else {
            current_obj -> free_list_link = next_obj;
        }
      }
    return(result);
}


template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
    char * result;
    size_t total_bytes = size * nobjs;
    size_t bytes_left = end_free - start_free;

    if (bytes_left >= total_bytes) {
        result = start_free;
        start_free += total_bytes;
        return(result);
    } else if (bytes_left >= size) {
        nobjs = bytes_left/size;
        total_bytes = size * nobjs;
        result = start_free;
        start_free += total_bytes;
        return(result);
    } else {
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
        // Try to make use of the left-over piece.
        if (bytes_left > 0) {
            obj * __VOLATILE * my_free_list =
                        free_list + FREELIST_INDEX(bytes_left);

            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }
        start_free = (char *)malloc(bytes_to_get);
        if (0 == start_free) {
            int i;
            obj * __VOLATILE * my_free_list, *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 <= __MAX_BYTES; i += __ALIGN) {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if (0 != p) {
                    *my_free_list = p -> free_list_link;
                    start_free = (char *)p;
                    end_free = start_free + i;
                    return(chunk_alloc(size, nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }
	    end_free = 0;	// In case of exception.
            start_free = (char *)malloc_alloc::allocate(bytes_to_get);
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return(chunk_alloc(size, nobjs));
    }
}

4、内存基本处理工具stl_uninitialized.h

STL定义有五个全局函数,作用于未初始化空间上。前两个函数是用于构造的construct()和用于析构的destroy(),另外三个函数是uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n()分别对应于高层次函数copy()、fill()、fill_n()。

(1)uninitialized_copy()

inline ForwardIterator uninitialized_copy(InputIterator first, InputIterator last,ForwardIterator result)接受三个参数:迭代器first指向输入端的起始位置、迭代器last指向输入端的结束位置、迭代器result指向输出端(欲初始化)的起始处

uninitialized_copy()会针对输出范围内[result,result+(last-first))的每一个迭代器都指向未初始化的内存,然后对输入范围内[first,last)的每一个迭代器i,调用construct(&*(result+(i-first)),*i),产生*i的复制品,放置于输出目的地[result,result+(last-first))范围。首先萃取result的value type,如果是POD(传统的C结构类型拥有copy函数)采用最有效率的复制手法copy(first,last,result),对non-POD采取最保险的做法一个一个元素地构造。

(2)uninitialized_fill()

inline void uninitialized_fill(ForwardIterator first, ForwardIterator last,const T& x)接受三个参数:迭代器first指向欲初始化空间的起始处、迭代器last指向欲初始化空间的结束处、x表示初值

uninitialized_fill()会针对输出范围内[first,last)的每一个迭代器i都指向未初始化的内存,调用construct(&*i,x),在i所指之处产生x的复制品。首先萃取first的value type,如果是POD(传统的C结构类型拥有copy函数)采用最有效率的初值填写手法fill(first,last,x),对non-POD采取最保险的做法一个一个元素地构造。

(3)uninitialized_fill_n()

inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,const T& x)接受三个参数:迭代器first指向欲初始化空间的起始处、n表示欲初始化空间的大小、x表示初值

uninitialized_fill_n()会针对输出范围内[first,first+n)的每一个迭代器i都指向未初始化的内存,调用copy constructor,在i所指之处产生x的复制品。首先萃取first的value type,如果是POD(传统的C结构类型拥有copy函数)采用最有效率的初值填写手法fill_n(first,n,x),对non-POD采取最保险的做法一个一个元素地构造。

uninitialized_copy()

template <class InputIterator, class ForwardIterator>
inline ForwardIterator
__uninitialized_copy_aux(InputIterator first, InputIterator last,
                         ForwardIterator result,
                         __true_type) {
  return copy(first, last, result);
}

template <class InputIterator, class ForwardIterator>
ForwardIterator
__uninitialized_copy_aux(InputIterator first, InputIterator last,
                         ForwardIterator result,
                         __false_type) {
  ForwardIterator cur = result;
  __STL_TRY {
    for ( ; first != last; ++first, ++cur)
      construct(&*cur, *first);
    return cur;
  }
  __STL_UNWIND(destroy(result, cur));
}


template <class InputIterator, class ForwardIterator, class T>
inline ForwardIterator
__uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result, T*) {
  typedef typename __type_traits<T>::is_POD_type is_POD;
  return __uninitialized_copy_aux(first, last, result, is_POD());
}

template <class InputIterator, class ForwardIterator>
inline ForwardIterator
  uninitialized_copy(InputIterator first, InputIterator last,
                     ForwardIterator result) {
  return __uninitialized_copy(first, last, result, value_type(result));
}

uninitialized_fill()

template <class ForwardIterator, class T>
inline void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
                         const T& x, __true_type)
{
  fill(first, last, x);
}

template <class ForwardIterator, class T>
void
__uninitialized_fill_aux(ForwardIterator first, ForwardIterator last,
                         const T& x, __false_type)
{
  ForwardIterator cur = first;
  __STL_TRY {
    for ( ; cur != last; ++cur)
      construct(&*cur, x);
  }
  __STL_UNWIND(destroy(first, cur));
}

template <class ForwardIterator, class T, class T1>
inline void __uninitialized_fill(ForwardIterator first, ForwardIterator last,
                                 const T& x, T1*) {
  typedef typename __type_traits<T1>::is_POD_type is_POD;
  __uninitialized_fill_aux(first, last, x, is_POD());

}

template <class ForwardIterator, class T>
inline void uninitialized_fill(ForwardIterator first, ForwardIterator last,
                               const T& x) {
  __uninitialized_fill(first, last, x, value_type(first));
}

uninitialized_fill_n()

template <class ForwardIterator, class Size, class T>
inline ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
                           const T& x, __true_type) {
  return fill_n(first, n, x);
}

template <class ForwardIterator, class Size, class T>
ForwardIterator
__uninitialized_fill_n_aux(ForwardIterator first, Size n,
                           const T& x, __false_type) {
  ForwardIterator cur = first;
  __STL_TRY {
    for ( ; n > 0; --n, ++cur)
      construct(&*cur, x);
    return cur;
  }
  __STL_UNWIND(destroy(first, cur));
}

template <class ForwardIterator, class Size, class T, class T1>
inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n,
                                              const T& x, T1*) {
  typedef typename __type_traits<T1>::is_POD_type is_POD;
  return __uninitialized_fill_n_aux(first, n, x, is_POD());

}

template <class ForwardIterator, class Size, class T>
inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,
                                            const T& x) {
  return __uninitialized_fill_n(first, n, x, value_type(first));
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值