SGI空间配置器

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

一般而言,cpp的内存申请与释放是通过newdelete来实现的

在SGI中,内存的申请与释放,对象的构建与析构由不同的函数库来实现

#include<stl_alloc.h> //负责内存的申请与释放
#include<stl_construct.h> //对象的构建与析构
在这里插入图片描述
不同对象的释放与开辟空间所耗损的时间不同。我们常用value_type()的方式获得迭代器所指之物的类别,之后通过_type_traits<T>来判断该类别是否值得通过调用析构函数来释放空间。

1.2SGI中空间的申请与释放

  • #include<stl_alloc.h> //负责内存的申请与释放
  • SGI通过malloc()与free()来完成对内存的申请与释放
  • 对于小区域可能造成的内存碎片的问题,SGI提供了两级配置器。其中,第一级配置器直接使用malloc()与free() 来实现空间的申请与释放;而第二级配置器视不同情况提供不同方法:
    大于128bytes的空间,直接调用一级配置器;而小于128bytes的空间,则采用**内存池(memory pool)**的处理办法
    在这里插入图片描述
    例:
list<int> mylist1;
mylist1.pop_back();   //因为sizeof(int) <= 128bytes,在释放空间时并不会直接将对象占有空间返回给堆区,而是由二级配置器调度,对象所占有空间资源返回给内存池



list<Base> mylist2;  
mylist2.pop_back();  //假设Base为自定类型,且sizeof(Base)> 128bytes;执行此语句时空间释放过程由一级配置器调度,执行free()使得对象占有的空间直接返回堆区


list<derive> mylist3;
mylist3.pop_back();   //假设derive为自定类型,且sizeof(derive)< 128bytes;此处空间配置方法同上

值得一提的是,上例中的list并非连续申请与释放空间,所以对其内存管理时只考虑单个对象的情况;当对于如vector容器的对空间连续释放与申请的情况,则需将整个内存空间视为统一整体进行管理

2.1一级配置器

2.1.1一级配置器中的函数

class _malloc_alloc_template
{
private:
  static void*oom_malloc (size_t n);
  static void*oom_realloc (void* p,size_t new_sz);
  static void(*_malloc_alloc_oom_handler)() ;
public:
  static void* allocate(size_t n);                                // malloc
  static void  deallocate(void* p,size_t n);                       //free 
  static void* reallocate(void* p, size_t old_sz,size_t new_sz); // realloc
  static void (*set_malloc_handler(void (*f)()))()

}

template<int inst>
void(*_malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = nullptr;

allocate函数 – 申请空间


static void* allocate(size_t n); 
{
   void* result = malloc(n); //malloc 申请n个空间
   if( result == nullptr ) //  空间申请失败时,改为调用oom_malloc()申请空间
   {
        result = oom_malloc(n);
   }
   return result;
}

deallocate函数 – 释放内存

  static void  deallocate(void* p,size_t n)
  {
    free(p);
  }

reallocate函数 – 追加空间

  static void* reallocate(void* p, size_t old_sz,size_t new_sz)
  {
     void* result = realloc(p,new_sz);
     if( nullptr == result )
     {
       result = oom_realloc(p,new_sz);
     }
    return result; 
  }

set_malloc_handler函数 – 设置一个注册函数,该函数用以释放多余空间

  //该语句中,函数返回类型为void (*f)(),函数名为set_malloc_handler,其形参为void (*f)();
  //如定义using PFUN = void (*)(),则下式也可写为static PFUN set_malloc_handler(PFUN f)
      static void (*set_malloc_handler(void (*f)()))()
      {
          void (*old)() = _malloc_alloc_oom_handler;
          _malloc_alloc_oom_handler = f;
          return old;
      }

oom_realloc 函数

void*oom_realloc (void* p,size_t new_sz)
{
       void* result = NULL;
       void(*_malloc_alloc_handler)() = nullptr;
       for(;;)
       {
          _malloc_alloc_handler = _malloc_alloc_oom_handler;
          
          if( _malloc_alloc_handler == nullptr) //若未有处理内存不足的情况
          {
          //该处的宏定义为 #define _THROW_BAD_ALLOC  std::cerr<<"out of memory"<<std::endl; exit(1);
              _THROW_BAD_ALLOC;
          }
          
          _malloc_alloc_handler(); //若有内存可分配,调用此函数以释放更多内存
          result = malloc(n); //继续分配内存
          if( result != nullptr ) 
          {
             return result;
          }
       }
}

配置器中 oom_realloc 函数应用的实例



//首先申请足够大的空间
char* cpa = (char*) malloc (sizeof (char) *10000000);
void fun()
{
  if (cpa != nullptr)
  {
    free(cpa);
  }
  cpa = nullptr;
  malloc_alloc::set_malloc_handler(nullptr) ;
}


  int main()
{
   malloc_alloc::set_malloc_handler(fun);
  
  int* p = (int*)malloc_alloc::allocate(sizeof(int)* 100);
  /*static void* allocate(size_t n); 
{
   void* result = malloc(n); //malloc申请n个空间
   if( result == nullptr ) // 假设此处申请失败,改为调用oom_malloc()申请空间
   {
        result = oom_malloc(n);
   }
   return result;
}*/
  

/*void*oom_realloc (void* p,size_t new_sz)
{
       void* result = NULL;
       void(*_malloc_alloc_handler)() = nullptr;
       for(;;)
       {
          _malloc_alloc_handler = _malloc_alloc_oom_handler; //获取注册函数,即fun();
          
          if( _malloc_alloc_handler == nullptr)
          {
              _THROW_BAD_ALLOC;
          }
          
          _malloc_alloc_handler(); //调用fun()函数以让系统释放更多内存,此时在fun()函数内set_malloc_handler已被置为空
          result = malloc(n); //继续分配内存
          if( result != nullptr ) 
          {
             return result;
          }
       }
}*/
  return 0;
}

oom_realloc 函数

 static void*oom_realloc (void* p,size_t new_sz)
 {
       void* result = NULL;
       void(*_malloc_alloc_handler)() = nullptr;
       for(;;)
       {
          _malloc_alloc_handler = _malloc_alloc_oom_handler;
          
          if( _malloc_alloc_handler == nullptr) //若未有处理内存不足的情况
          {
          //该处的宏定义为 #define _THROW_BAD_ALLOC  std::cerr<<"out of memory"<<std::endl; exit(1);
              _THROW_BAD_ALLOC;
          }
          
          _malloc_alloc_handler(); //若有内存可分配,调用此函数以释放更多内存
          result = realloc(p,new_sz); //继续分配内存
          if( result != nullptr ) 
          {
             return result;
          }
       }
}

3.1二级配置器

二级配置器多了一些机制,避冤太多小块内存造成的内存碎片问题。同时,配置时的额外负担(overhead)也是要注意的一个问题。额外负担无法避免,系统需要这些额外空间来管理申请的内存,如下图所示。
在这里插入图片描述
另外,调用malloc()的时间开销也不小;以上两个原因决定了二级配置器需要用内存池来管理内存

SGI第二级配置器的做法是,如果申请空间大于 128 bytes,就移交给第一级配置器处理;如果小于128 bytes,则以内存( memory pool)管理﹐此法又称为次级配置( sub-allocation)︰每次配置一大瑰t噫醴﹐亚徘濩黝仪之自由串列(free-

  • 何为内存池?

加粗样式
实现上,内存池类似哈希表,如上图中下标0的指针指向一个每个节点占8bytes的链表;而下标1的指针指向一个每个节点占16bytes的链表;下标2的指针指向一个每个节点占24bytes的链表······直至15下标指向每个节点占128bytes的链表;在分配内存时按最佳匹配方式分配。

如要申请4字节空间,检查发现下标0所指向的单个节点空间足够,则会在该区域分配相应内存(取整,直接为其分配8字节空间)

内存池本身的空间也是通过向堆区申请所得来的

内存池的代码实现

enum { ALIGN = 8 } ;
enum { _MAX_BYTES= 128 } ;
enum {_NFREELISTS = __MAX_BYTES /__ALIGN }; // 16

template< bool threads,int inst >
class _default_alloc_template
{
private :
  union obj
  {
    union obj *free_list_link;  //用法如同链表中的next
     //char client_data[1];
  };
private:
  static obj* volatile free_list [__NFREELISTS];
  static char* start_free;
  static char* end_free;
  static size_t heap_size; //申请的总空间数


  static size_t ROUND_UP(size_t bytes);// 为所申请字节取整,如 2 ->8 
  static size_t FREELIST_INDEX(size_t bytes) ; //所分配链表下标,如 2-> 0
  static char* chunk_alloc(size_t size,int& nobjs);
  static void* refill(size_t size) ;   //填充某一链表
public:
  static void* allocate(size_t size) ;//申请空间
  static void deallocate(void* p, size_t n) ;  //释放空间
  static void* reallocate(void* p, size_t old_sz,size_t new_sz); 
};

volatile 关键字 - 该值直接从内存中取出,不需要优化到寄存器中取出

属性的初始化

template<bool threads,int inst>
typename _default_alloc_template<threads,inst>::obj* volatile _default_alloc_template<threads,inst>::free_list[__NFREELISTS] = {0};

template<bool threads,int inst>
char*_default_alloc_template<threads,inst>:: start_free = nullptr;

template<bool threads,int inst>
char*_default_alloc_template<threads,inst>:: end_free = nullptr;

template<bool threads,int inst>
size_t default_alloc_template<threads,inst>:: heap_size = 0;


ROUND_UP函数 – 提升为8的倍数

在二进制中,若x为8的倍数,则其末三位一定为0

static size_t ROUND_UP(size_t bytes)
{ 
   return (bytes + _ALIGN - 1) & ~(_ALIGN - 1);
   /*
   _ALIGN - 1 = 7 = 0000 0111;
     ~求反后得       1111 1000
     通过与(bytes + _ALIGN - 1)相与实现取 8的倍数          
   */
}

FREELIST_INDEX函数 – 所分配链表下标

static size_t FREELIST_INDEX(size_t bytes)
{
   return (bytes + _ALIGN -1) / _ALGIN - 1;
}

chunk_alloc函数 – 从内存池中获取空间

static char* chunk_alloc(size_t size,int& nobjs);
{
   char* result = NULL;
   size_t total_bytes = size * nobjs; //申请的总空间大小
   size_t bytes_left = end_free - start_free; //内存池的字节个数
    
   if( bytes_left >= total_bytes ) //内存池大小满足需求
   {
      result = start_free;
      start_free = start_free + total_bytes;
      return result;
   }
   else if(bytes_left >= size)  //若不足,检查是否至少满足00000000000000000000000000000000000000000000000000000000000000一个size块的空间
   {
      nobjs = bytes_left / size; //修改申请空间块数为最大可满足量
      total_bytes = size * nobjs; //申请的总空间大小随之改变
      //以下三行代码同上
      result = start_free;
      start_free = start_free + total_bytes;
      return result;
   }
   else  //若仍然无法满足
   {
   //此时内存池中的资源已经不足,需要额外申请空间
     size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); //准备尝试申请一个大块内存
     if(bytes_left > 0)  //若池中还有未利用的小块内存,将其分配出去
     {
       //bytes_left一定为8的倍数;例如 bytes_left == 8 时,my_free_list会指向下标为0的链表处
       //将余下的内存块链接至对应的链表处(头插法)
        obj* volatile* my_free_list = free_left + 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 (NULL== start_free)//如若系统堆区空间不足,则尝试从更大节点空间的链表处获取空间
     {
        obj* volatile* my_free_list = NULL;
        obj* p = NULL;
        //  for(int i = size; i <= 128; i += 8)
        for(int i = size; i <= _MAX_BYTES; i += _ALIGN)
        {
            my_free_list = free_list_link + FREELIST_INDEX(i);
            p = *my_free_list;
            
            if( p != NULL )//当前下标的的链表不为空
            {
                *my_free_list = p->free_list_link;
                start_free = (char*)p; //将分出的节点视为新的内存池
                end_free = start_free + i;
                return chunk_alloc(size,nobjs); //在新的内存池中尝试重新申请空间
            }
        }
        // 没有更多链表资源可供使用
        //尝试调用一级空间配置器
        /*
         一级配置器调用顺序会如下:allcate()->oom_realloc()->_malloc_alloc_oom_handler();
         _malloc_alloc_oom_handler()在已注册的情况下,会调用注册好的函数释放一定空间;
         若未注册,则内存分配彻底失败,系统抛出异常,退出;
         _THROW_BAD_ALLOC  std::cerr<<"out of memory"<<std::endl; exit(1);
        */
        start_free = malloc_alloc::allcate(bytes_to_get);
     }
     end_free = start_free + bytes_to_get;
     heap_size += bytes_to_get;
     return chunk_alloc(size,nobjs); //在新的内存池中尝试重新申请空间
   }
   return result;
} 

refill函数 – 填充某一链表

static void* refill(size_t size)
{
  int nobjs = 20;  //STL在填充链表时默认预填充20个节点
  char* chunk = chunk_alloc(size,nobjs);
  if( nobjs == 1 ) //若分配的内存块只有1个
  {
     return chunk;
  }
  obj* volatile* my_free_list = NULL;
  obj* result = (obj*) chunk;  //分配空间的首地址,result是要返回给用户的空间
  obj* current_obj = NULL,*next_obj = NULL;
  int i = 0;
  my_free_list = free_list + FREELIST_INDEX(size);
  *my_free_list = next_obj = (obj*) (chunk + size); //除去首个内存块外的第一个地址
  for( i = 1;; ++i )
  {
     current_obj = next_obj;
     next_obj = (obj*)((char*)next_obj + size );                                                                                                                                                                                        
     if( i == nobjs - 1 ) //当前为最后一块内存块
     {
        current_obj->free_list_link = NULL;
        break;
     }
     current_obj->free_list_link = next_obj;
  }
  return result; 
}

allocate函数 – 分配空间

static void* allocate(size_t size)
{
  if( size > (size_t) _MAX_BYTES) //所申请空间大于128bytes
  {
     return malloc_alloc::allocate(size); // 移交给一级配置器调配
  }
 obj* result = nullptr;
 obj* volatile* my_free_list = nullptr; 
 my_free_list = free_list + FREELIST_INDEX(size); //指向对应大小链表的首地址
 result = *my_free_list;
 if( nullptr == result)
 {
    void* r = refill(ROUND_UP(size));
    return r;
 }
 *my_free_list = result->free_list_link;   //已分配出一个节点,指针后移
 return result;
}

deallocate函数 – 释放空间

static void deallocate(void* p, size_t n) 
{
   if( n > (size_t)_MAX_BYTES) // 所释放空间大于128bytes
   {
      malloc_alloc::deallocate(p,n); //调用一级配置器
      return;
   }
   obj* q = (obj*) p;
   obj* volatile* my_free_list = free_list + FREELIST_INDEX(n); //指向对应大小链表的首地址
   q->free_list_link = *my_free_list->free_list_link;
   *my_free_list = q;
   return;
}

reallocate函数 – 重分配空间

static void* reallocate(void* p, size_t old_sz, size_t new_sz); 
{
   if( old_sz> (size_t)_MAX_BYTES && new_sz>(size_t)_MAX_BYTES  ) // 所释放空间大于128bytes
   {
      return malloc_alloc::reallocate(p,old_sz,new_sz); //调用一级配置器
   }
   if( ROUND_UP(old_sz) == ROUND_UP(new_sz) )
   {
     return p;
   }
   
   size_t sz = old_sz < new_sz ? old_sz : new_sz;
   void* s = allocate(new_sz);
   memmove(s,p,sz);
   deallocate(p,old_sz);
   return s;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值