allocator有标准的接口,SGI为了实现优化,并没有按照标准接口来实现。但是并不会造成大的影响。在实现中为容器配置了SGI的allocator。并且实现了标准的接口,以便用户使用。
具备次配置力(sub-allocation)的SGI空间配置器
SGI STL的配置器与众不同,也与标准规范不同,其名称是allor而非allocator,而且不接受任何参数。换句话说,如果你要在程序中明白采用SGI配置器,则不能采用标准写法:
vector<int, std::allocator<int> > iv;//VC or CB
vector<int, std::alloc> iv; //in GCC
SGI STL allocator未能符合标准规格,这个事实通常不会给我们带来困扰,通常我们使用缺省的空间配置器,很少需要自行指定配置器名称,而SGI STL每一个容器都已经指定其缺省的空间配置器为alloc。例如下面的vector声明:
template <class T, class Alloc = alloc>
class vector {//...
};
SGI特殊的空间配置器,std ::alloc
首先讨论我们平时使用的内存配置和释放的操作:
class Foo {...};
Foo* pf = new Foo;//配置内存, 构造内存
delete pf;//将对象析构,释放内存
new分为两段:
-
调用::operator new 配置内存;
-
调用Foo::Foo() 构造对象内容。
delete:
-
调用Foo::~Foo() 把对象析构。
-
调用::operator delete 释放内存。
为了精密分工,STL allocator决定将这两阶段操作区分开来。内存配置操作由alloc:allocate()负责,内存释放操作由alloc:deallocate()负责:对象构造操作由::construct()负责,对象析构操作由::destroy ( )负责.
// 为了使用new place
template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
new (__p) _T1(__value);//new place for assignment
}
template <class _T1>
inline void construct(_T1* __p) {
new (__p) _T1();//default ctor
}
在这里需要注意语法 new(pointer) typename(value); 这是在已经分配了内存pointer的空间上进行初始化。place new
.
流程如上所示,destroy进行了特化版本,char不需要析构。在泛化版本中判断析构函数是否为trivial(翻译是无关疼痒 其实就是默认析构函数的意思)。这时候也是什么也不做。进行优化。
空间的配置与释放 std::alloc
其中设计中考虑的因素如下:
-
向system heap要求空间。
-
考虑多线程(multi-threads)状态。
-
考虑内存不足时的应变措施。
-
考虑过多“小型区块”可能造成的内存碎片(fragment)问题。
C++内存配置基本操作是::operator new ->和C中malloc()相当。SGI以malloc()和free完成内存的配置和释放。
配置的基本流程如下:
一级调度核心代码:
template <int __inst>
class __malloc_alloc_template
{
private:
static void* _S_oom_malloc(size_t);
static void* _S_oom_realloc(void*, size_t);
static void (* __malloc_alloc_oom_handler)();
public:
static void* allocate(size_t __n)
{
void* __result = malloc(__n);
if (0 == __result) __result = _S_oom_malloc(__n);//分配失败时调用
return __result;
}
static void deallocate(void* __p, size_t /* __n */)
{
free(__p);
}
static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
{
void* __result = realloc(__p, __new_sz);
if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);
return __result;
}
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>::_S_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; }//没有配置时 打印退出
//# define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
(*__my_malloc_handler)();
__result = realloc(__p, __n);
if (__result) return(__result);
}
}
第二级配置器__default_alloc_tem plate剖析
第二级配置器多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的其实不仅是内存碎片,配置时的额外负担(overhead)也是一个大问题。
常规设计如上。
SGI第二级配置器的做法是,如果区块够大,超过128 bytes时,就移交第一级配置器处理。
当区块小于128 bytes时,则以内存池(memory pool)管理,此法又称为次层配置(sub-allocation ):每次配置一大块内存,并维护对应之自由链表(free-list )。下次若再有相同大小的内存需求,就直接从free-lists中拨出。如果客端释还小额区块,就由配置器回收到free-lists中—是的,别忘了,配置器除了负责配置,也负责回收。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客端要求30 bytes,就自动调整为32 bytes ),并维护16个free-fists,各自管理大小分别为8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88,96, 104, 1 l2, 120, 128 bytes的小额区块·free-lists的节点结构如下:
关注以上结构体为一个union类型,要么存放数据,要么指向下一个union结构。这样做能够节省空间。分配出去后不再获得。
关键函数分析
allocate
static void* allocate(size_t __n)
{
void* __ret = 0;
if (__n > (size_t) _MAX_BYTES) {
__ret = malloc_alloc::allocate(__n);//使用一级调度器
}
else {
_Obj* __STL_VOLATILE* __my_free_list
= _S_free_list + _S_freelist_index(__n);//取得对应链表头
// Acquire the lock here with a constructor call.
// This ensures that it is released in exit or during stack
// unwinding.
/*REFERENCED*/
_Lock __lock_instance;//加锁
_Obj* __RESTRICT __result = *__my_free_list;
if (__result == 0)//对应链表头为空
__ret = _S_refill(_S_round_up(__n));//重新分配
else {
*__my_free_list = __result -> _M_free_list_link;
__ret = __result;//取得结果
}
}
return __ret;
};
deallocate
/* __p may not be 0 */
static void deallocate(void* __p, size_t __n)
{
if (__n > (size_t) _MAX_BYTES)
malloc_alloc::deallocate(__p, __n);
else {
_Obj* __STL_VOLATILE* __my_free_list
= _S_free_list + _S_freelist_index(__n);
_Obj* __q = (_Obj*)__p;
// acquire lock
/*REFERENCED*/
_Lock __lock_instance;//使用类加锁
/* _NOTHREADS */
__q -> _M_free_list_link = *__my_free_list;//放回到当前的链表中 不会真正的做释放
*__my_free_list = __q;
// lock is released here
}
}
refill
在allocate发现free list没有可用的区块就会调用refill().新的空间将取自内存池(经由chunk_alloc()完成)。缺省取得20个新节点(新区块),但万一内存池空间不足,获得的节点数(区块数)可能小于20.
template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
int __nobjs = 20;
char* __chunk = _S_chunk_alloc(__n, __nobjs);//需要分配的大小
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __result;
_Obj* __current_obj;
_Obj* __next_obj;
int __i;
if (1 == __nobjs) return(__chunk);//get return only to return
//more than one set to the list first of all, find the locate
__my_free_list = _S_free_list + _S_freelist_index(__n);
/* Build free list in chunk */
__result = (_Obj*)__chunk;
*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);
//insert into 插入到free list中
for (__i = 1; ; __i++) {
__current_obj = __next_obj;//because the pool is null now
__next_obj = (_Obj*)((char*)__next_obj + __n);
if (__nobjs - 1 == __i) {//最后一个 指向空
__current_obj -> _M_free_list_link = 0;
break;
} else {
__current_obj -> _M_free_list_link = __next_obj;
}
}
return(__result);
}
内存池
这个函数非常巧妙的递归调用,省去了重复的代码。
在最后必须到case 1或者case 2中才能真正的返回。
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,
int& __nobjs)
{
char* __result;
size_t __total_bytes = __size * __nobjs;
size_t __bytes_left = _S_end_free - _S_start_free;
if (__bytes_left >= __total_bytes) {//case 1完全分配
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
} else if (__bytes_left >= __size) {//case 2部分分配
__nobjs = (int)(__bytes_left/__size);
__total_bytes = __size * __nobjs;
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
} else {//case 3 不足以分配1个
size_t __bytes_to_get =
2 * __total_bytes + _S_round_up(_S_heap_size >> 4);//(new size = old_size / 16) + 2*need add size
// Try to make use of the left-over piece.
if (__bytes_left > 0) {
_Obj* __STL_VOLATILE* __my_free_list =
_S_free_list + _S_freelist_index(__bytes_left);
((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
*__my_free_list = (_Obj*)_S_start_free;
}
_S_start_free = (char*)malloc(__bytes_to_get);
if (0 == _S_start_free) {//case 3.1 malloc失败
size_t __i;
_Obj* __STL_VOLATILE* __my_free_list;
_Obj* __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.
//从比该free list 大的块中回收快 比如目前为8 则从16 24...回收一个再分配出去
//使用递归_S_chunk_alloc 共享case 1 case 2代码
for (__i = __size;
__i <= (size_t) _MAX_BYTES;
__i += (size_t) _ALIGN) {
//try to free the free_list which bigger than _size
__my_free_list = _S_free_list + _S_freelist_index(__i);
__p = *__my_free_list;
if (0 != __p) {
*__my_free_list = __p -> _M_free_list_link;
_S_start_free = (char*)__p;
_S_end_free = _S_start_free + __i;
return(_S_chunk_alloc(__size, __nobjs));//guan
// Any leftover piece will eventually make it to the
// right free list.
}
}
_S_end_free = 0; // In case of exception.
//case 3.2 遍历完任然没有空闲的块 只能尝试一级调度里面释放内存重新分配的方式
_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
// This should either throw an
// exception or remedy the situation. Thus we assume it
// succeeded.
}
_S_heap_size += __bytes_to_get;
_S_end_free = _S_start_free + __bytes_to_get;
return(_S_chunk_alloc(__size, __nobjs));
}
}
以上为使用visio画的大致流程图。最终分配成功。
内存基本处理工具
举例说明, 以下以uninitialized_copy()为例子。
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
uninitialized_copy(_InputIter __first, _InputIter __last,
_ForwardIter __result)
{
return __uninitialized_copy(__first, __last, __result,
__VALUE_TYPE(__result));
}
template <class _InputIter, class _ForwardIter, class _Tp>
inline _ForwardIter
__uninitialized_copy(_InputIter __first, _InputIter __last,
_ForwardIter __result, _Tp*)
{
typedef typename __type_traits<_Tp>::is_POD_type _Is_POD;//是否为POD类型
return __uninitialized_copy_aux(__first, __last, __result, _Is_POD());
}
template <class _InputIter, class _ForwardIter>
inline _ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
_ForwardIter __result,
__true_type)//关注这个参数 默认构造器 所以直接使用内存拷贝
{
return copy(__first, __last, __result);
}
template <class _InputIter, class _ForwardIter>
_ForwardIter
__uninitialized_copy_aux(_InputIter __first, _InputIter __last,
_ForwardIter __result,
__false_type)//使用构造函数
{
_ForwardIter __cur = __result;
__STL_TRY {
for ( ; __first != __last; ++__first, ++__cur)
construct(&*__cur, *__first);
return __cur;
}
__STL_UNWIND(destroy(__result, __cur));
}
//以下为上面相关宏的说明
//类型转换
template <class _Iter>
inline typename iterator_traits<_Iter>::value_type*
__value_type(const _Iter&)
{
return static_cast<typename iterator_traits<_Iter>::value_type*>(0);
}
//以下为特化函数
inline char* uninitialized_copy(const char* __first, const char* __last,
char* __result) {
memmove(__result, __first, __last - __first);
return __result + (__last - __first);
}
inline wchar_t*
uninitialized_copy(const wchar_t* __first, const wchar_t* __last,
wchar_t* __result)
{
memmove(__result, __first, sizeof(wchar_t) * (__last - __first));
return __result + (__last - __first);
}
POD Plain Old Data 也就是标量型别(scalar types)或传统的C struct型别。POD型别必然拥有trivial ctor/dtor/copy/assignment函数,因此,我们可以对POD型别采用最有效率的初值填写手法,而对non-POD型.别采取最保险安全的做法.