SGI的特殊空间配置器

Posted RenewDo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SGI的特殊空间配置器相关的知识,希望对你有一定的参考价值。

SGI的空间配置器allocator只是简单的new和delete的一层包装,没有提供效率的强化。

而一般C++内存配置和释放操作如下:

class  Foo  { ... }  

Foo  *pf = new Foo;

delete pf; 

new算式:1)使用new配置内存,2)使用Foo构造对象

delelte算式: 1)使用~Foo()将对象析构 ,2)使用delete释放内存

 

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

    如下图是STL allocator的构造:

 

                                | <stl_constuct.h>   //全局函数:constrcut(),destory()负责对象的构造和析构

        技术分享技术分享<memory>----- |<stl_alloc.h>         // 一、二级配置器(alloc)

                                |<stl_uninitialized.h> //全局函数:un_initialized_copy()、un_initialized_fill()、un_initialized_fill_n() 复制填充大块内存数据

                                                               //这些函数会考虑效率,最差用constrcut(),最佳时用memmove()直接进行内存移动

                                                                

一、<stl_constuct.h>

constrcut()

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

destory()

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

template<class ForwardIterator,class T>//版本二
inline void destroy(ForwardIterator first,ForwardIterator last,T*)
{
   typedef  typename _type_traits<T>::has_trivial_destructor tivial_destructor();
_destory_aux(first,last,trivial_destructor);
}

版本二会根据数值型别判断,如果是_true_type就什么也不做, _false_type会循环每个对象依次调用版本一的destroy

二、<stl_alloc.h> 

SGI对此的设计哲学如下:

        1. 向 system heap 要求空间。

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

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

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

为了处理小型区块可能造成的内存碎块,SGI设计两层配置器,如下:

第一级:   (大于128bytes)                                                                                                    第二级

template <int inst>                                                                                       template<bool threads,int inst>

class _malloc_alloc_template{..}                                                                      class _default_alloc_template{..};

其中:                                                                                                              其中:  

1、allocate()直接使用malloc()                                                                          1、维护16个 free lists,负责16种小型区块的次配置能力

deallocate()直接使用free()                                                                               内存池(memory pool)以malloc配置而得,内存不足转第一级配置器 

2、模拟C++的set_new_handler()处理内存不足                                                  2、需求区块大于128bytes,转第一级配置器

 

       由于第一级,第二级只是以128bytes判断,所以需要用simple_alloc接口进行封装,使得由bytes转为元素大小。

     下面看看第一级配置器的内存不足处理

template <int inst>  
class __malloc_alloc_template { 
 private://处理内存不足函数
static void *oom_malloc(size_t);  
static void *oom_realloc(void *, size_t);  
 static void (* __malloc_alloc_oom_handler)();

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

    第二级配置器:

    当区块小于128bytes,每次配置一大块内存,并维护其对应的自由链表,配置器负责分配与回收。

     free-lists节点结构:  

      为了尽最大可能减少内存的使用, 这里使用一个union ,如果使用第一个成员, 则指向另一个相同的union obj ,如果使用第二个成员, 则指向实际的内存区域 ,这样就实现了链表结点只使用一个指针的大小空间, 却能同时做索引和指向内存区域!!!!!

union obj {  
      union obj * free_list_link;  
      char client_data[1];    /* The client sees this. */  
};  

  第二级配置器中主要有

ROUND_UP(x):负责将输入的字节数上调至8的倍数,如下:

                (x+7)&~7  // +7是上升一个8的倍数,然后~7是清除/8的余数

 allocate():  首先判断区块大小,大于128就调用第一级,小于128时就检查 free list, free list里有可用的区块就直接用,若没有就将区块大小上升至8的倍数,然后用refill()为 free list重新填充空间。如下:

static void * allocate(size_t n)  
{  
    obj * __VOLATILE * my_free_list;  
    obj * __RESTRICT result;  
  
    // 大于128 就调用第一级配置器  
    if (n > (size_t) __MAX_BYTES) {  
        return(malloc_alloc::allocate(n));  
    }  
    // 寻找 16 个free lists中适当的一个  
    my_free_list = free_list + FREELIST_INDEX(n);  
    #ifndef _NOTHREADS  
    /*REFERENCED*/  
    lock lock_instance;  
    #endif  
    result = *my_free_list;  
    if (result == 0) {  
        // 没找到可用的 free list,准备重新填充 free list  
        void *r = refill(ROUND_UP(n));  
        return r;  
    }  
    // 调整 free list  
    *my_free_list = result -> free_list_link;  
    return (result);  
};
my_free_list = free_list + FREELIST_INDEX(n);  //my_free_list会定位到数组16个元素的某一个(里面存的是对应链表头的地址)

result = *my_free_list; //result 是一个指针,会指向对应的链表头

deallocate(): 首先判断区块大小,大余128就调用第一级配置器,小于128就找相应的freelist,将区块回收
static void deallocate(void *p, size_t n)  //p是待回收的内存地址,n是对应freelist 要回收的地方
{  
    obj *q = (obj *)p;  
        obj * __VOLATILE * my_free_list;  
  
    // 大于 128 就调用第一级配置器  
         if (n > (size_t) __MAX_BYTES) {  
                malloc_alloc::deallocate(p, n);  
            return;  
        }  
  
    // 寻找对应的 free list  
        my_free_list = free_list + FREELIST_INDEX(n);  
        // acquire lock  
        #ifndef _NOTHREADS  
        /*REFERENCED*/  
        lock lock_instance;  
        #endif /* _NOTHREADS */  
  
    // 调整 free list,回收区块  
        q -> free_list_link = *my_free_list;  
        *my_free_list = q;  //把这段待放的内存块插入链表头
        // lock is released here  
} 

refill():发现freelist没有可用空间时,从内存池(chunk_alloc()完成),缺省取20,内存池不足时,可能小于20.

// 返回一个大小为 n 的对象,并且有时候会为适当的 free list 增加节点  
// 假设 n 已经适当上调至 8 的倍数  
/* We hold the allocation lock. */  
template <bool threads, int inst>  
void* __default_alloc_template<threads, inst>::refill(size_t n)  
{  
    int nobjs = 20;  
    // 调用 chunk_alloc(),尝试取得 nobjs 个区块作为 free list 的新节点  
    // 注意参数 nobjs 是 pass by reference  
    char * chunk = chunk_alloc(n, nobjs);  
    obj * __VOLATILE * my_free_list;  
    obj * result;  
    obj * current_obj, * next_obj;  
    int i;  
  
    // 如果只获得一个区块,这个区块就分配给调用者用,free list无新节点  
    if (1 == nobjs) return(chunk);  
    // 否则准备调整 free list,纳入新节点  
    my_free_list = free_list + FREELIST_INDEX(n);  
  
    // 以下在 chunk 空间内建立 free list  
    result = (obj *)chunk; // 这一块准备返回给客户端  
  
    // 以下导引 free list 指向新配置的空间(取自内存池)  
    *my_free_list = next_obj = (obj *)(chunk + n);  
    // 以下将 free list的各节点串接起来  
    for (i = 1; ; i++) { // 从 1 开始,因为第 0 个将返回给客端  
        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);  
}  
 

 chunk_alloc():是从内存池取内存给freelist的。如果有足够块数就直接返回,不足时就返回整数块数。如果连一块都不足时,首先考虑将内存池剩余空间编入freelist,然后向堆栈申请空间,连堆栈都没有空间时,再回freelist里找没用到且区块够大的,最后还不行才抛异常。

// 假设 size 已经适当上调至 8 的倍数  
// 注意参数 nobjs 是 pass by reference  
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);  
        // 以下试着让内存池中的残余零头还有利用价值(零头也应该是 8 的倍数)  
        if (bytes_left > 0) {  
            // 内存池内还有一些零头,先配给适当的free list  
            // 首先寻找适当的 free list  
            obj * __VOLATILE * my_free_list =  
                        free_list + FREELIST_INDEX(bytes_left);  
  
            // 调整 free list,将内存池中的残余空间编入  
            ((obj *)start_free) -> free_list_link = *my_free_list;  
            *my_free_list = (obj *)start_free;  
        }  
  
        // 配置 heap 空间,用来补充内存池  
        start_free = (char *)malloc(bytes_to_get);  
        if (0 == start_free) {  
            // heap空间不足,malloc()失败  
            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.  
            // 试着检视我们手上拥有的东西。这不会造成伤害。我们不打算尝试配置  
            // 较小的区块,因为那在多进程(multi-process)机器上容易导致灾难  
            // 以下搜寻适当的 free list  
            // 所谓适当是指"尚有未用区块,且区块够大"之 free list  
            for (i = size; i <= __MAX_BYTES; i += __ALIGN) {  
                my_free_list = free_list + FREELIST_INDEX(i);  
                p = *my_free_list;  
                if (0 != p) { // free list内尚有未用区块  
                    // 调整free list以释出未用区块  
                    *my_free_list = p -> free_list_link;  
                    start_free = (char *)p;  
                    end_free = start_free + i;  
            // 递归调用自己,为了修正 nobjs  
                    return(chunk_alloc(size, nobjs));  
            // 注意,任何残余零头终将被编入适当的free-list中备用  
                }  
            }  
  
    end_free = 0;   // 如果出现意外,到处都没内存可用  
    // 调用第一级配置器,看看 out-of-memory 机制能否尽点力  
    start_free = (char *)malloc_alloc::allocate(bytes_to_get);  
        // 这会导致抛出异常(exception),或内存不足的情况获得改善  
        }  
        heap_size += bytes_to_get;  
        end_free = start_free + bytes_to_get;  
    // 递归调用自己,为了修正 nobjs  
        return(chunk_alloc(size, nobjs));  
    }  
}  

 

以上是关于SGI的特殊空间配置器的主要内容,如果未能解决你的问题,请参考以下文章

STL 之 空间配置器(allocator)

SGI版本空间配置器

SGI STL内存配置器:内存泄漏?

SGI STL内存配置器存在内存泄漏吗?

SGI STL 空间配置器

STL源码剖析第二章---配置器