SGI STL内存池
SGI STL包含了一级空间配置器和二级空间配置器,其中一级空间配置器allocator采用malloc和free来管理内存,和C++标准库中提供的allocator是一样的,但其二级空间配置器allocator采用了基于freelist自由链表原理的内存池机制实现内存管理。
一级空间配置器:
allocate:负责给容器开辟内存空间 => malloc
deallocate:负责释放容器的内存空间 => free
construct:负责给容器构造一个对象 => 定位new实现,在指定的内存上构造处对象(拷贝构造)
destroy:负责析构容器的对象 => p->~T()
二级空间配置器:
内存池:主要采用链表数组的方式。
重要类型和变量定义
// 内存池的粒度信息
//chunk块的最小单位
enum {_ALIGN = 8};
//chunk块的最大单位
enum {_MAX_BYTES = 128};
//16个chunk块类型,从8,16,24...128共16个
enum {_NFREELISTS = 16};
// 每一个内存chunk块的信息,第一个变量指针指向邻接的下一chunk地址
union _Obj {
union _Obj* _M_free_list_link;
char _M_client_data[1]; /* The client sees this. */
};
// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为0
static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS];
// Chunk allocation state. 记录内存chunk块的分配情况
//指向可用内存空间的起始地址
static char* _S_start_free;
//指向可用内存空间的末尾
static char* _S_end_free;
//目前分配的总内存大小
static size_t _S_heap_size;
template char* __default_alloc_template<__threads, __inst>::_S_start_free = 0;
template char* __default_alloc_template<__threads, __inst>::_S_end_free = 0;
template size_t __default_alloc_template<__threads, __inst>::_S_heap_size = 0;
重要的辅助函数
/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t _S_round_up(size_t __bytes){
return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1));
}
/*返回 __bytes 大小的chunk块位于 free-list 中的编号,例如8字节大小的chunk块就在静态链表的0,16在1以此类推*/
static size_t _S_freelist_index(size_t __bytes) {
return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
}
内存池的管理函数
// 分配内存的入口函数
static void* allocate(size_t __n)
如果需要分配的内存空间大于128个字节,就直接使用malloc函数,不再使用内存池。
// 分配内存的入口函数
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;
};
// 负责把分配好的chunk块进行连接,添加到自由链表当中
static void* _S_refill(size_t __n)
// 负责把分配好的chunk块进行连接,添加到自由链表当中
static void* _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;
//如果只剩下一个chunk块,就无需chunk块的连接了
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);
}
// 分配相应内存字节大小的chunk块,并且给下面三个成员变量初始化
static char* _S_chunk_alloc(size_t __size, int& __nobjs)
// 分配相应内存字节大小的chunk块,并且给下面三个成员变量初始化
static char* _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);
//如果备用的内存空间不够分配,判断是否够一个chunk块,如果够就能分配几个就分配几个
} 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 {
/*
如果备用的内存空间连所需的一个chunk块都分配不了,就先将备用剩余的内存空间以头插法的方式给正好那么大的chunk链表
然后重新去开辟内存空间,每次新分配的内存空间会越来越大。
*/
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);
//如果内存分配失败,就遍历静态链表,去更大的chunk块处借。
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.
}
}
/*如果更大的chunk块也没有了,只能尝试再次申请看是否有其他线程释放内存,
还是申请不到的话就去找是否有设内存释放回调函数,如果没有就抛异常,
有的话就死循环调用内存释放的回调函数,直到有可用的内存空间
*/
_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));
}
}
// 把chunk块归还到内存池
static void deallocate(void* __p, size_t __n);
// 把chunk块归还到内存池
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 */
//头插法的方式将不用的chunk块放到对应链表的头上,供下一次申请时使用。
__q -> _M_free_list_link = *__my_free_list;
*__my_free_list = __q;
// lock is released here
}
}
// 内存池扩容函数
template void*__default_alloc_template::reallocate(void* __p,size_t __old_sz,size_t __new_sz);
// 内存池扩容函数
template void*__default_alloc_template::reallocate(void* __p,size_t __old_sz,size_t __new_sz);
{
void* __result;
size_t __copy_sz;
if (__old_sz > (size_t) _MAX_BYTES && __new_sz > (size_t) _MAX_BYTES) {
return(realloc(__p, __new_sz));
}
if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
__result = allocate(__new_sz);
__copy_sz = __new_sz > __old_sz? __old_sz : __new_sz;
memcpy(__result, __p, __copy_sz);
deallocate(__p, __old_sz);
return(__result);
内存池的优点:
防止小块内存频繁的分配,释放,造成内存很多的碎片出来,内存没有更多的连续的大内存块。所以应用对于小块内存的操作,一般都会使用内存池来进行管理。
SGI STL二级空间配置器内存池的实现优点:
1.对于每一个字节数的chunk块分配,都是给出一部分进行使用,另一部分作为备用,这个备用可以给当前字节数使用,也可以给其它字节数使用。
2.对于备用内存池划分完chunk块以后,如果还有剩余的很小的内存块,再次分配的时候,会把这些小的内存块再次分配出去,备用内存池使用的干干净净!
3.当指定字节数内存分配失败以后,有一个异常处理的过程,遍历bytes - 128字节所有的chunk块进行查看,如果哪个字节数有空闲的chunk块,直接借一个出去,
如果上面操作失败,还会调用oom_malloc这么一个预先设置好的malloc内存分配失败以后的回调函数,没设置抛异常malloc throw bad_alloc
设置了就死循环调用回调函数,直到有可以用内存空间来分配(*oom_malloc_handler)();malloc