SGI STL空间配置器-第二级空间配置器

相比第一级配置器,第二级配置器多了一些机制,避免小额区块造成内存的碎片。小额区块不仅仅是碎片的问题,配置时的额外负担也是一个大问题。因为区块越小,额外负担所占的比例就越大,就越显得浪费。

额外负担是指动态分配内存块的时候,位于其头部的额外信息,包括记录内存块大小的信息以及内存保护区(判断是否越界),即索取任何一块内存时,都要得有一些“税”要交给系统。


SGI STL第二级配置器具体实现思想

  1. 如果要分配的区块大于128bytes,则移交给第一级配置器处理。
  2. 如果要分配的区块小于128bytes,则以内存池管理(memory pool),又称之次层配置(sub-allocation):每次配置一大块内存,并维护对应的自由链表(free-list)。下次若有相同大小的内存需求,则直接从free-list中取。如果有小额区块被释放,则由配置器回收到free-list中。配置器除了负责配置还负责回收。并且为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客户端要求13bytes,就自动调整为16bytes),并维护16个free-lists,各自管理大小为8,16,...,128bytes的小额区块。
用union表示链表节点结构:
union obj {  
      union obj * free_list_link;  
      char client_data[1];    /* The client sees this. */  
}; 
以下是第二级配置器总体实现代码:
enum {__ALIGN = 8}; //小型区块的上调边界
enum {__MAX_BYTES = 128}; //小型区块的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; //free-list的个数
template <bool threads, int inst>
class __default_alloc_template {
private:
  static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1)); //将bytes上调至8的倍数
  }
private:
  union obj {
        union obj * free_list_link;
        char client_data[1];    /* The client sees this. */
  };
private:
    static obj * __VOLATILE free_list[]; //16个自由链表
    //以下根据区块的大小,决定使用第n号free-list,编号从0开始。
    static obj * __VOLATILE free_list[__NFREELISTS]; 
  static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
  }


  // 返回一个大小为n的对象,并可能加入大小为n的其他区块到free-list
  static void *refill(size_t n);
  // 配置一大块空间,课容纳nobjs个大小为“size”
的区块
  // 如果配置nobjs个区块有所不变,nobjs可能会降低
  static char *chunk_alloc(size_t size, int &nobjs);


  // Chunk allocation state.
  static char *start_free; //内存池开始位置
  static char *end_free;  //内存池结束位置
  static size_t heap_size;

public:
 /* n must be > 0      */
  static void * allocate(size_t n){...}


 /* p may not be 0 */
  static void deallocate(void *p, size_t n){...}
 static void * reallocate(void *p, size_t old_sz, size_t new_sz);
};

//静态成员的定义和初始值设定
template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::start_free = 0;//内存池起始位置


template <bool threads, int inst>
char *__default_alloc_template<threads, inst>::end_free = 0;//内存池结束位置


template <bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;
template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj * __VOLATILE
__default_alloc_template<threads, inst> ::free_list[
# ifdef __SUNPRO_CC
    __NFREELISTS
# else
    __default_alloc_template<threads, inst>::__NFREELISTS
# endif
] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

空间配置函数allocate()

空间配置器函数allocate(),此函数首先判断区块的大小,大于128bytes就调用第一级配置器,小于128bytes就检查对应的free-list,从这里获取内存。如果free-list内有可用的区块,直接拿来用就好,如果没有的话,就将区块大小上调至8的倍数,然后调用refill来为free-list填充空间。
下面是allocate()代码:
 /* n must be > 0      */  
  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-list中适当的一个
    my_free_list = free_list + FREELIST_INDEX(n);  
    result = *my_free_list;  
    if (result == 0) {  
        //没有可用的区块,调用fefill来填充free-list
        void *r = refill(ROUND_UP(n));  
        return r;  
    }  
    //调整free-list
    *my_free_list = result -> free_list_link;  
    return (result);  
  };  
这里注意:每次都是从对应的free-list的头部取出可用的内存块。然后对free-list进行调整,使上一步拨出内存的下一个节点变为头节点
*my_free_list = result -> free_list_link;
当对该内存块使用deallocate()函数进行回收时,也要对free-list进行调整。
上述过程图示如下:

空间释放函数deallocate()
如果需要回收的区块大于128bytes,则调用第一级配置器。
如果需要回收的区块小于128bytes,找到对应的free -list,将区块回收。注意是通过调整free-list链表将区块放入free -list的头部。
 /* p may not be 0 */  
  static void deallocate(void *p, size_t n)  
  {  
    obj *q = (obj *)p;  
    obj * __VOLATILE * my_free_list;  
  
    if (n > (size_t) __MAX_BYTES) {  
        malloc_alloc::deallocate(p, n);  
        return;  
    }  
    my_free_list = free_list + FREELIST_INDEX(n);  
    // acquire lock  
#       ifndef _NOTHREADS  
        /*REFERENCED*/  
        lock lock_instance;  
#       endif /* _NOTHREADS */  
    q -> free_list_link = *my_free_list;  
    *my_free_list = q;  
    // lock is released here  
  } 

refill()-为free list填充空间


当发现对应的free-list没有可用的空闲区块时,就需要调用此函数重新填充空间。新的空间将取自于内存池(由chunk_alloc()完成)。

缺省状况下取得20个新区块,但是如果内存池空间不够,取得的节点数就有可能小于20。

下面是SGI STL中的refill源代码:

/返回一个大小为n的对象,并且有时候会为适当的free-list增加节点/  
/假设n已经上调至8的倍数/  
template <bool threads, int inst>  
void* __default_alloc_template<threads, inst>::refill(size_t n)  
{  
    int nobjs = 20;  
    char * chunk = chunk_alloc(n, nobjs); //调用chunk_alloc(),尝试去的nobjs个区块作为free-list增加节点
    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++) {//将各节点串接起来(注意,索引为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);  
} 


内存池

从内存池取空间给free-list使用,是chunk_alloc()的工作:

主要思想:

1、chunk_alloc()以end_free-start_free来判断内存池的水量(size_t bytes_left = end_free - start_free; ),如果水量充足,直接调用20个区块给free-list。

    if (bytes_left >= total_bytes) {  
        result = start_free;  
        start_free += total_bytes;  
        return(result); 
2、如果水量不足怎么办?(为什么忽然想污一下。。。)即不足以提供20个区块,但还足够供应一个以上的区块,就是说还可以满足程序使用,就把这些少许空间拿去。

    } else if (bytes_left >= size) {  
        nobjs = bytes_left/size;  
        total_bytes = size * nobjs;  
        result = start_free;  
        start_free += total_bytes;  
        return(result); 

3、如果内存池连一个区块空间都无法供应,那只能调用malloc从heap中申请内存,heap才是水的源头,这时候,新水量的大小为需求量的两倍。从heap中配置内存时,配置的大小为需求量的两倍再加上一个随配置次数逐渐增加的附加量。

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.  
                }  
            }  

4、万一!heap中也没有内存了,malloc失败怎么办? 这时候chunk_alloc()到处寻找有没有“目前没有用的区块”,且足够大,找到的话一切好办,拿来用,找不到就调用第一级配置器,使用第一级配置器中的out-of-memory处理机制(类似c++中new-handle机制),执行释放下内存之类的动作,或许还有机会得到些内存,如果可以,就成功。不可以,则只能发出bad_alloc异常。也算是尽力了。。

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));  
    }  
内存池实际动作示意图:


程序一开始,客户调用chunk_alloc(32,20),因为此时内存池和free list空间均不够,于是调用malloc从heap配置40个32bytes区块,其中一个供使用,另一个交给free_list[3]维护。剩余的20个留给内存池。接下来调用chunk_alloc(64,20), 此 时 free_list[7] 空空如也,必须向记忆池要求支持。记忆池只够供应  (32*20)/64=10 个 64bytes区块,就把这 10 个区块传回,第 1 个交给客端,余 9个由 free_list[7] 维护。此时记忆池全空。接下来再呼叫chunk_alloc(96, 20),此时 free_list[11] 空空如也,必须向记忆池要求支持,而记忆池此时也是空的,于是以malloc()配 置 40+n(附加量)个 96bytes 区块,其中第 1 个交出,另 19 个交给 free_list[11] 维护,余 20+n(附加量)个区块留给记忆池……


猜你喜欢

转载自blog.csdn.net/mmshixing/article/details/51672434
今日推荐