STL源码剖析之配置器(下)
2011-06-04 13:34 Aga.J 阅读(398) 评论(0) 编辑 收藏 举报11 因为考虑到内存碎片的情况,所以SGI采用【双层级配置器】,第一级配置器直接使用malloc和free。而第二级则: 当 申请区超过128 byte 时,调用一级配置器。当小于128时,采用复杂的memory pool(内存池),下面介绍SGI的方法(有其自己的空间分配等)
#ifdef __USE_MALLOC
…
Typedef __malloc_alloc_template<0> malloc_alloc;
Typedef malloc_alloc alloc; // 令alloc为第一级配置器
#else
Typedef __default_alloc_template<__NODE_ALLOCATOR_THREAD,0> alloc;
// 令alloc为二级配置器
#endif
也就是说malloc alloc template就是第一级配置器模板,default alloc template就是第二级配置器模板。
Template<int inst>
Class __malloc_alloc_template //第一级配置器类模板
{
//allocate使用malloc
//deallocate使用free
//模拟C++的setNewHandler处理内存不足的情况
};
Template<bool threads,int inst>
Class __default_alloc_template //第一,二级配置器
{
//如果需求区大于128byte,就转用一级配置器
//否则二级要防止碎片发生
// 维护16个 free list
// 负责16种小块的次配置能力
// 内存池用alloc配置得到
};
然后再封装配置器功能接口
Template<class T, class Alloc>
Class simple_alloc
{
Public:
Static T *allocate(size_t n) {return 0== n? 0:(T*)Alloc::allocate(n*sizeof(T)) ;}
Static T *allocate(void) {return (T*) Alloc::allocate(sizeof(T));}
Static void deallocate(T* p,size_t n) {if (0!=n) Alloc::deallocate(p,n*sizeof(T));}
Static void deallocate(T *p){ Alloc::deallocate(p,sizeof(T));}
//将调用传递给配置器的成员函数,可能是第一级也可能是第二级
};
SGI的STL容器全部使用这个simple_alloc接口
Template<class T, class Alloc=alloc> //缺省使用alloc为配置器
Class vector
{
Protected:
Typedef simple_alloc<value_type,Alloc> data_allocator;
//使用这个simple_alloc就可以隐藏底层的byte字节参数的传入,使用类型即可
Void deallocate()
{
If()
Data_allocator::deallocate( start, end_of_storage-start);
//start是value type,第二个参数则是指针差
}
};
/*********
首先利用条件编译决定是使用二级配置器还是一级配置器,并且使用typedef来统一配置器的命名为alloc(默认情况下所有容器使用该配置器)
然后为每个容器加上该配置器(配置器也作为模板参数,但是默认情况下是预定义的配置器)
因为默认配置器内的allocate定义等,需要传入byte大小,所以封装这些复杂的输入,抽取出一个simple_alloc的类,来完成基本的byte转换。
最后就在每个容器中定义这个simple_alloc类,完成初始化分配;
以上是一个比较完整的一级二级配置器的设计结构,暂时不涉及底层的实际分配情况。
下面介绍实际的__malloc_alloc_template和__default_alloca_template
*******/
12 所以最底层的实现是上面两个一级和二级配置器的实现,simple_alloc只是一个高层接口,而容器则是通过这样的配置器来完成内存空间的分配。
一级配置器 malloc_alloc_allocator 比二级配置器平均速度慢(每次都要请求空间分配),但是空间利用率高(分配大块,满块),而且是thread safe.
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)();
Public:
Static void *allocate(size_t n) //实际的allocate函数(simple_alloc里面定义的)
{
Void *result = malloc(n); //实际上是调用C的malloc来实现,一旦分配失败,则使用oom_malloc
If( 0 == result) result=oom_malloc(n); //无法满足需求时,使用oom_malloc
Return result;
}
Static void deallocate(void *p,size_t )
{
Free(P); //直接使用C的free
}
Static void *reallocate(void *p, size_t oldSize, size_t new_sz)
{
Void *result = realloc(p,new_sz); //还是调用底层的realloc
If (0==result) result=oom_realloc(p,new_sz); //下面有这个函数的实现
Return result;
}
Static void (* set_malloc_handler(void (*f) () ) ) ()
{
Void (*old)() = __malloc_alloc_oom_handler; //转换为void指针
__malloc_alloc_oom_handle=f; //然后指向f
Return (old); //返回这个指针
}
//模仿C++的set_new_handler,我们可以自己指定out of memory handler
};
Template<int inst>
Void (*__malloc_alloc_template<inst>::__malloc_alloc_com_handler ) () =0;
Template<int inst>
Void *__malloc_alloc_template<inst>::oom_malloc(size_t n) //oom_malloc的实现
{
Void (*my_malloc_handler)();
Void *result;
For(;;) //循环直到分配成功
{
My_malloc_handler = __malloc_alloc_oom_handler;
//指向预定义的例程地址,通过调用static void(* set_malloc_handler(void(*f)()))()来设定执行内容
If( 0== my_malloc_handler)
{__THROW_BAD_ALLOC; } //未定义
else
(*my_malloc_handler)(); //如果定义了,就调用例程
Result = malloc(n); //result = realloc(p,n); //并且重新分配,知道分配成功
If( result) return (result);
}
}
//因为不是使用C++的::operator new,所以不能直接使用C++的new handler只能仿真
//在该(分配失败处理)中,不断调用“内存不足处理例程”,也就是这里的my_malloc_handler,期望在某次调用后,可以获得内存完成任务,而如果“该例程没有被客户端设置,则直接抛出错误”。
Typedef __malloc_alloc_template<0> malloc_alloc;
这是一级配置器的分配过程及其分配失败的处理过程(模拟了C++的new handler,完成自己的异常处理程序)
13 二级配置器 __default_alloc_template
二级配置器能帮助我们避免太多小额区块所造成的内存碎片,但是小额碎片在配置时会有标志区域(这会带来额外的负担),所以小额区域越小,负担越大。(既需要一个pa,又需要使用cookie来记录内存的大小)
二级配置器的做法是:如果块够大,超过128byte时,则移交第一级配置器处理,当块小于128时,则以内存池(memory pool)管理:每次配置一大块内存,并维护所对应的free list,如果下次有相同大小的请求,则直接从free list中取出。如果释放了 ,则回收到free list中。(为了配置方便,二级配置器会将小额内存需求量上调至8的倍数)然后维护16个free list,每个list管理 8,16。Xxxx的区域大小
一种free list的定义可能是这样
Struct Node
{
Int size;
Char client_data[];
Node* next;
};
这样一来,每个节点都需要一个额外的指针来指向下一个节点,这会带来新的负担。所以使用下面的定义方式
Union obj
{
Union obj *free_list_link;
Char client_data[];
};
利用union的好处,第一个字段可以指向另一个obj,第二个字段可以指向 实际区块。(怎样做到节省了内存?只有空闲时才使用第一个字段,被分配时会使用第二个字段),也就是说,元素的free—list只保存了下一个节点的地址,也就是使用了第一个obj,然后再被申请后,就会使用第二个字段(这时候也没必要维护它在free中的位置),所以很巧妙。
实现:
Enum { __ALIGN = 8};
Enum { __MAX_BYTES = 128};
Enum { __NFREELISTS = __MAX_BYTES/__ALIGN};
Template<bool threads, int inst>
Class __default_alloc_template
{
Private:
Static size_t ROUND_UP(size_t bytes) //将byte上调到8的倍数
{ return ((bytes)+8 – byte+8 )% 8}
{ return ( ((bytes)+__ALIGN-1) & ~(__ALIGN -1 ) ) ; }
//思想:加上8后,进一步对齐,然后再取出最后3位,保证没有多余的数,这样就可以整除8了
Private:
Union obj //高效的节点定义方法
{
Union obj *free_list_link;
Char client_data[1];
};
Private:
Static obj *volatile free_list[__NFREELISTS];
//max128,每个块对齐8,所以需要128/8个list槽
Static size_t FREELIST_INDEX (size_t bytes) //根据所需内存块的大小,决定使用
{ return (((bytes)+__ALIGN -1 )/ __ALIGN -1 ); }//第n号的free-list
Static void *refill (size_t n); //加入free list
Static char *chunk_alloc(size_t size, int &nobjs); //配置一个可以容纳多个size的区域
Static char *start_free; //内存池起始位置
Static char *end_free;
Static size_t heap_size;
Public:
Static void *allocate( size_t n) {}
Static void deallocate ( void *p, size_t n){}
Static void *reallocate( void *p, size_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>
__defualt_alloc_template<threads,inst>::obj *volatile __default_alloc_template<threads,inst>::free_list __NFRESSLISTS ={0};
//二级配置器,使用内存池的概念,主要是维护一个free list列表,每个free list都对应一种大小的空闲区域块。(辅助函数是上调块大小,返回对应free list号),然后主要完成allocate,deallocate,reallocate(这些函数再调用私有分配函数完成)
做为配置器,__default_alloc_template拥有配置器的标准接口函数allocate(这就是为什么定义一个新的分配接口) 当调用allocate时,先判断块的大小,如果大于128就使用第一级的配置器,小于128就根据实际byte大小检查对应的free list,如果有可用的块,就拿来使用,如果没有的话,就上调至8倍数的边界,然后refill,
Static void *allocate(size_t n)
{
Obj *volatile *my_free_list;
Obj *result;
If ( n> (size_t)__MAX_BYTES) //判断请求的块的大小,根据大小来决定使用一级配置器还是二级配置器
{
Rerurn (malloc_alloc::allocate(n)); //调用一级配置器
}
My_free_list = free_list + FREELIST_INDEX(n); //指向数组的一个空闲的slot,这个slot的可分配块的大小和n对齐到8的倍数后的值适配。
Result = my_free_list; //得到这个list下面的第一个空闲块
If( result == 0) //没有可用的空间
{
Void *r= refill( ROUND_UP(n));
Return r;
}
*my_free_list = result->free_list_link; //指向下一个未分配的node
Return result;
}
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);
q->free_list_link = *my_free_list;
*my_free_list=q; //见下图说明
//q里面的脏数据怎么解决???delete []q.data;
}
前面allocate时,如果free list没有可用的块时,则使用refill,即为free list重新填充空间。新的空间有chunk_alloc完成
Template<bool threads, int inst>
Void* __default_alloc_template<threads,inst>::refill(size_t n)
{
Int nobjs=20;
Char *chunk = chunk_alloc( n,nobjs); //分配20个新节点,大小为n
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);
Result = (obj*) chunk; //第一块准备返回给客户
*my_free_list = next_obj =(obj*)(chunk+n); //free指向新配置的空间
For (i=1;;i++) //从第一个节点开始,连接上所有节点
{
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);
}
内存池 memory pool chunk_alloc的源码
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
{
….
}
}
内存池的操作:
1 计算 内存池(一个数组,每个slot维护一条list,该list的节点统一大小,slot之间的list大小有递增关系)的剩余空间, 计算 所需要的总数空间
2 如果 空间满足,则返回其实位置,并重新设定剩余空间大小
3 如果不能满足,但是足够提供一个以上的区块,则给出这么多个区块的空间
4 如果一个区块都不能满足,??
14 内存基本处理工具
STL定义了5个全局的函数来操作 已经分配但是未初始化 的空间。
Construct,destroy,uninitialized_copy, uninitialized_fill, uninitialized_fill_n
1) uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result)
如果目的地 result,result+(last-first) 未初始化,那么这个函数会使用 复制构造函数为first到last之间的对象产生一个copy [ construct ( & * (result +(i-first)), *i ) ]
2) uninitialized_fill ( ForwardIterator first, ForwardIterator last, const T& x);
如果first到last指向的目标区域都指向没有初始化的内存,那么该函数会产生x类型的复制品。
3) uninitailized_fill_n
作者:Aga.J
出处:http://www.cnblogs.com/aga-j
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
个人学习笔记仅供本人记录知识所用,不属发表性文章。