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));
    }
}  
时间: 2024-08-07 13:06:22

SGI的特殊空间配置器的相关文章

SGI版本空间配置器

1.STL中的空间配置器 在STL中,空间配置器分了2组,分别为一级空间配置器和二级空间配置器,但是它们都有自己各自运用的场合:一般说来,一级空间配置器一般分配的空间大于128B,二级空间配置器的分配空间为小于128B. 其中SGI 中的STL的空间配置器设计哲学如下: (1).向system heap要求申请空间 (2).考虑多线程的状态 (3).考虑内存不足时的应变措施 (4).考虑过多"小型区块"可能造成的内存碎片问题 在剖析源码时,为了将问题控制在一定的复杂度内,以下源码皆排除

STL源码剖析 — 空间配置器(allocator)

前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配置器,配置的对象,是内存.(以下内容来自<STL源码剖析>) 空间配置器的标准接口 根据STL的规范,allocator的必要接口 各种typedef 1 allocator::value_type 2 allocator::pointer 3 allocator::const_pointer 4

STL 之 空间配置器(allocator)

一.SGI 标准的空间配置器,std::allocator SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳. 它只是把C++的操作符::operator new和::operator delete做了一层简单的封装而已. 二.SGI 特殊的空间配置器,std::alloc 由于SGI 标准的空间配置器只是把C++的操作符::operator new和::operator delete做了一层简单的封装,没有考虑到任何效率上的

2.1 空间配置器(allocator)

空间配置器隐藏在一切组件之后. 1.1 设计一个简单的空间配置器 根据STL的规范,以下是allocator的必要接口: allocator::value_type allocator::pointer allocator::const_pointer allocator::reference allocator::const_reference allocator::size_type allocator::difference_type allocator::rebind // 一个嵌套的(

简单的空间配置器实现

这一节用到的小知识: 1.ptrdirr_t: ptrdiff_t是C/C++标准库中定义的一个与机器相关的数据类型.ptrdiff_t类型变量通常用来保存两个指针减法操作的结果.ptrdiff_t定义在stddef.h(cstddef)这个文件内.ptrdiff_t通常被定义为long int类型. 2.non-trivial constructor/destructor: 意思是"非默认构造函数/析构函数",这里的non-trivial指不是编译器自动生成的(函数)维基百科 我认为

STL源码分析--空间配置器 第一级配置器

一.SGI STL配置器简介 SGI STL的配置器与众不同,它与标准规范不同.如果要在程序中明确使用SGI配置器,那么应该这样写: [cpp] view plaincopyprint? vector<int,std::alloc> iv; 他的名字是alloc,而且不接受任何参数.标准配置器的名字是allocator,而且可以接受参数. SGI STL的每一个容器都已经指定了缺省配置器:alloc.我们很少需要自己去指定空间配置器.比如vector容器的声明: [cpp] view plai

STL源码分析--第二级空间配置器

本文讲解SGI STL空间配置器的第二级配置器. 相比第一级配置器,第二级配置器多了一些机制,避免小额区块造成内存的碎片.不仅仅是碎片的问题,配置时的额外负担也是一个大问题.因为区块越小,额外负担所占的比例就越大. 额外负担是指动态分配内存块的时候,位于其头部的额外信息,包括记录内存块大小的信息以及内存保护区(判断是否越界).要想了解详细信息,请参考MSVC或者其他malloc实现. SGI STL第二级配置器具体实现思想 如下: 如果要分配的区块大于128bytes,则移交给第一级配置器处理.

2.SGI STL第二级空间配置器__default_alloc_template的chunk_alloc函数

SGISTL默认使用二级空间配置器,当需要配置的区块大于128 bytes时SGI STL调用一级空间配置器,一级空间配置器的allocate函数直接使用malloc分配内存,deallocate函数直接使用free释放内存.当需要配置的区块小于128 bytes时SGI STL调用二级空间配置器. 相比于一级空间配置器简单粗暴的内存使用方法,二级空间配置器对内存的使用显得精细很多. 二级空间配置器的具体用法请看书,我就不抄书了,只对二级空间配置器中容易糊涂的地方写一下我的理解. 内存池和fre

1、空间配置器

看侯捷老师的<STL源码剖析>有一段时间了,打算自己整理一下思路,试着实现一下.主要目的有两个:1.巩固自己对源码的理解,让自己更加深刻的体会其中各种机制的奥妙.2.通过实现这些优秀的算法,来提高自己的“内功”修养. 关于空间配置器,首先作以下几点说明: 1.空间配置器即为程序分配存储空间.这里的存储空间包括内存,也包括磁盘或者其它辅助存储介质. 2.C++一般使用new算式进行存储空间配置.使用delete操作符释放分配的内存.其中的new算式内含两个阶段操作:(1).调用::operato