一.SGI特殊的空间配置器 std::alloc
一般而言,cpp的内存申请与释放是通过new
与delete
来实现的
在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;
}