STL之空间配置器
1、基本概念
空间配置器,就是用于管理容器的内存的分配与释放,而不用容器自己去处理。
2、标准接口
空间配置器,根据STL的规范,是有一套统一的接口的。但是实现这些接口的空间配置器,只能有限地搭配当前各个版本的STL,因为每个版本都做了自己的特殊处理,并没有严格遵循这一接口,SGI版本的甚至自行实现了一套接口,我们要看的就是SGI版本的。
3、SGI空间配置器
SGI空间配置器,与标准规范和其他版本的都有很大的不同,是一个专属的、拥有次级配置能力的、性能优越的特殊配置器。
c++的关键字new和delete用于对象的创建和释放,但都包含两个阶段的操作。其中,new包含:(1)调用::operator new配置内存;(2)调用构造函数构造对象内容;delete包含:(1)调用析构函数对对象内容进行析构;(2)调用::operator delete释放内存。SGI空间配置器将这些操作进行了拆分,使用不同的接口进行处理,包括:用于配置内存的alloc::allocate();用于释放内存的alloc::deallocate();用于构造对象的::construct();用于析构对象的::destroy()。
4、构造工具
构造工具construct,用于构造对象,使用placement new来实现,源码如下:
template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
new ((void*) __p) _T1(__value);
}
template <class _T1>
inline void _Construct(_T1* __p) {
new ((void*) __p) _T1();
}
template <class _T1, class _T2>
inline void construct(_T1* __p, const _T2& __value) {
_Construct(__p, __value);
}
template <class _T1>
inline void construct(_T1* __p) {
_Construct(__p);
}
5、析构工具
析构工具destroy,用于对象的析构,这里既可以对单一对象进行析构,也可对一个区间内的多个对象进行析构。对于单个对象,直接调用对象的析构函数即可;对于多个对象,需要判断对象的析构函数是否是默认的析构函数这种无关紧要的析构函数,若是则什么也不做,反之才进行调用,防止由于对象数量过大的问题造成的性能的损失。以下为实现源码:
template <class _Tp>
inline void _Destroy(_Tp* __pointer) {
__pointer->~_Tp();
}
template <class _ForwardIterator>
void
__destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
{
for ( ; __first != __last; ++__first)
destroy(&*__first);
}
template <class _ForwardIterator>
inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {}
template <class _ForwardIterator, class _Tp>
inline void
__destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*)
{
typedef typename __type_traits<_Tp>::has_trivial_destructor
_Trivial_destructor;
__destroy_aux(__first, __last, _Trivial_destructor());
}
template <class _ForwardIterator>
inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
__destroy(__first, __last, __VALUE_TYPE(__first));
}
template <class _Tp>
inline void destroy(_Tp* __pointer) {
_Destroy(__pointer);
}
template <class _ForwardIterator>
inline void destroy(_ForwardIterator __first, _ForwardIterator __last) {
_Destroy(__first, __last);
}
6、空间的配置与释放
c++使用::operator new()和::operator delete()进行内存的分配与释放,本质相当于c的malloc()和free(),这里采用是c的接口。同时,为了解决小型区块 可能造成的内存碎片问题,这里采用双层级配置器的设计。
7、第一级配置器
这级配置器使用c接口malloc()、free()、realloc()来进行内存的配置、释放和重配置的操作,并实现了应对内存不足时的new-handler机制。当出现内存不足时,如有设置对应处理函数,就不断循环调用处理函数,然后尝试配置内存,直到成功;若未设置,则直接抛出异常,并终止程序。源码如下:
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_malloc(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; }
(*__my_malloc_handler)();
__result = malloc(__n);
if (__result) return(__result);
}
}
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; }
(*__my_malloc_handler)();
__result = realloc(__p, __n);
if (__result) return(__result);
}
}
8、第二级配置器
这级配置器的作用,就是为了解决内存碎片的问题,同时也减轻了配置时的额外负担。其设计结构是以多个free-list和一个内存池来实现的。
8.1、free-list
这里以8为基数,创建16个free-list,每个free-list内部对象的大小分别对应8、16、32…一直到128。对于外部请求的小额区块,会自动调整为8的倍数,其实现算法为:
enum {_ALIGN = 8};
static size_t
_S_round_up(size_t __bytes)
{ return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
同时,链表的节点必须额外维护一个指向下一个节点的指针,会造成额外的负担,这里为了解决这一问题,采用了union的设计,共用内存,从第一个字段获取的就是下一个节点,从第二个字段获取的就是实际的区块,实现如下:
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
8.1、空间配置
第二级配置器在进行空间配置时,会根据待分配区块的大小进行判断,根据情况使用不同的处理逻辑,其实现原理是:
1. 当处理的区块大于128bytes时,直接交由第一级配置器进行处;
2. 找到区块大小对应的free-list,若free-list中有可用区块,则直接分配出去,反之则跳转到步骤3;
3. 将区块大小跳转为8的整数,然后调用refill(),为free-list重新填充空间。
以上步骤的实现源码为:
/* __n must be > 0 */
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.
# ifndef _NOTHREADS
/*REFERENCED*/
_Lock __lock_instance;
# endif
_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;
};
8.2、free-list重新填充
当free-list没有可用区块时,需要对free-list进行重新填充,不仅仅处理本次的配置请求,也会对后续的请求做预留,其实现步骤如下:
1. 默认请求20个区块的大小来填充,首先检测内存池是否足够分配,若足够就直接分配,转到步骤6,反之则转到步骤2;
2. 当不够分配20个时,检测实际可分配的个数是否至少有1个,是则分配,转到步骤6,反之则转到步骤3;
3. 当内存池中一个也不够时,此时将内存池内剩余的空间先分配给对应的free-list,然后向系统请求内存来填充内存池,若请求成功则再分配free-list填充所需的,转到步骤6,反之则转到步骤4;
4. 此时系统内存不足,无法分配内存,就只能查找其他free-list是否有足够大且闲置的区块,有则拿一块放入内存池,然后转到步骤1,反之则转到步骤5;
5. 此时其他free-list也没有可以用的区块,这里不考虑多个小区块合并的情况,就只能向第一级配置器请求,看第一级配置器的oom机制是否解决问题,要么分配成功,转到步骤6,要么抛出异常,中断程序;
6. 此时已经成功获取到多个区块的内存,将第一块分配出去,其他区块就放入free-list备用,此时完成free-list的重新填充。
以上步骤的实现源码如下:
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);
__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);
for (__i = 1; ; __i++) {
__current_obj = __next_obj;
__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);
}
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) {
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
} else if (__bytes_left >= __size) {
__nobjs = (int)(__bytes_left/__size);
__total_bytes = __size * __nobjs;
__result = _S_start_free;
_S_start_free += __total_bytes;
return(__result);
} else {
size_t __bytes_to_get =
2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
// 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) {
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.
for (__i = __size;
__i <= (size_t) _MAX_BYTES;
__i += (size_t) _ALIGN) {
__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));
// Any leftover piece will eventually make it to the
// right free list.
}
}
_S_end_free = 0; // In case of exception.
_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));
}
}
8.3、内存池
内存池是free-list后备,是free-list内存不足的首要考虑对象。当内存池中的内存不足,需要向系统引来活水,以应对后续的内存消耗,这里会向系统请求free-list填充所需大小的两倍加上一个累计值的大小的内存,把free-list填充所需的分配出去后,剩余的就留作备用,其实现算法如下:
size_t __bytes_to_get =
2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
_S_heap_size += __bytes_to_get;
_S_heap_size一开始为0,记录着内存池累计向系统请求的内存,通过这个累计值,可以减少向系统请求内存的频率以降低性能消耗。
8.4、空间释放
第二级配置器空间释放的时候也只处理小区块,其实现步骤如下:
1. 检测区块大小,若大于128bytes则交由第一级配置器处理,反之转到步骤2;
2. 查找区块对应的free-list,将区块插入表头即可。
以上步骤的实现源码如下:
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
# ifndef _NOTHREADS
/*REFERENCED*/
_Lock __lock_instance;
# endif /* _NOTHREADS */
__q -> _M_free_list_link = *__my_free_list;
*__my_free_list = __q;
// lock is released here
}
}
9、内存基本处理工具
对于对象的初始化,除了使用construct()方法外,STL还提供了uninitialized_copy()、uninitialized_fill()、uninitialized_fill_n()这三个接口,分别对应STL的算法copy()、fill()、fill_n()。这三个接口都具有“commit or rollback”语义,即要么构造出所有必要元素,要么什么也不构造。
对于要操作的数据类型的不同,也会采取不同的策略。POD,即Plain Old Data,指与c兼容的数据类型。其在c++里的标准定义为:将对象的各字节拷贝到一个字节数组中,然后再将它重新拷贝回原来的对象所占的存储区中,测试对象的值应与原先的保持一致。因而,对于POD类型可以采用最有效的初值填写手法,对于non-POD类别需要采用最保险安全的做法。
同时,为了进一步提升性能,对char*和wchar_t*进行了特化,直接使用memmove方法,从而达到最大的效率。
下面以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;
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));
}
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);
}
10、综合结果
这里使用了很多技巧,以满足容器对象最基础的操作,隐藏在容器的底层,不需要容器自己再自行处理相关内容。
上一篇: 老鼠与猫的交易