STL空间配置器
Posted wanglelelihuanhuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL空间配置器相关的知识,希望对你有一定的参考价值。
一、STL为什么需要空间配置器?(内存有关)1、解决内存碎片(外碎片)
空间配置器解决外碎片,引入内碎片
内碎片:比如需要5个字节,向上对齐取整new了8个字节,那么这3个未使用的为内碎片。
比如list、map、set、hashtable等push(new)或pop(delete)操作都有小空间。2、提高效率:频繁分配小块内存导致效率低,每一次都有系统调用。
二、STL空间配置器框架
一级空间配置器和二级空间配置器
1、一级空间空间配置器是对malloc、free和realloc的封装
(1)allocate调用malloc
(2)deallocate调用free
(3)模拟C++的set_new_hander()
set_new_hander():分配内存失败处理函数句柄。在内存空间分配失败的情况下,如果设置了该句柄,则不会直接抛异常,而是执行这个处理函数,因为__malloc_alloc_oom_handler是全局的,可在外释放一些空间,那么有可能下次循环就会分配成功。不断的尝试分配,直至分配成功;否则(未设置该句柄),抛出异常。
STL空间配置器使用的是malloc分配空间,所以设计的是set_malloc_hander()。
///
// 一级空间配置器(malloc/realloc/free)
//
// 内存分配失败以后处理的句柄handler类型
typedef void(*ALLOC_OOM_FUN)();
template <int inst>
class __MallocAllocTemplate
private:
//static void (* __sMallocAllocOomHandler)();
static ALLOC_OOM_FUN __sMallocAllocOomHandler;
static void * OomMalloc(size_t n)
ALLOC_OOM_FUN handler;
void* result;
//
// 1:分配内存成功,则直接返回
// 2:若分配失败,则检查是否设置处理的handler,
// 有则调用以后再分配。不断重复这个过程,直到分配成功为止。
// 没有设置处理的handler,则直接结束程序。
//
for (;;)
handler = __sMallocAllocOomHandler;
if (0 == handler)
cerr<<"out of memory"<<endl;
exit(-1);
handler();
result = malloc(n);
if (result)
return(result);
static void *OomRealloc(void* p, size_t n)
// 同上
ALLOC_OOM_FUN handler;
void* result;
for (;;)
handler = __sMallocAllocOomHandler;
if (0 == handler)
cerr<<"out of memory"<<endl;
exit(-1);
(*handler)();
result = realloc(p, n);
if (result) return(result);
public:
static void * Allocate(size_t n)
void *result = malloc(n);//第一级空间配置器直接使用malloc()
//以下无法满足需求时,改用OomMalloc()
if (0 == result) result = OomMalloc(n);
return result;
static void Deallocate(void *p, size_t /* n */)
free(p);//第一级空间配置器直接使用free()
static void* Reallocate(void *p, size_t /* old_sz */, size_t new_sz)
void * result = realloc(p, new_sz);//第一级空间配置器直接使用realloc()
//以下无法满足需求时,改用OomRealloc()
if (0 == result) result = OomRealloc(p, new_sz);
return result;
//模拟C++的set_new_hander()
static void (* SetMallocHandler(void (*f)()))()
void (* old)() = __sMallocAllocOomHandler;
__sMallocAllocOomHandler = f;
return(old);
;
// 分配内存失败处理函数的句柄函数指针
template <int inst>
ALLOC_OOM_FUN __MallocAllocTemplate<inst>::__sMallocAllocOomHandler = 0;
typedef __MallocAllocTemplate<0> MallocAlloc;
2、二级空间配置器
(1)维护16个自由链表(free_list)
(2)需求大于128bytes,直接调用一级空间配置器
(3)需求小于128,检查对应的free_list,如果free_list有可用的直接用,否则需求量上调至8的倍数(例如client要求30bytes,就自动调整为32bytes)并维护16个free_list。然后调用refill(),为free_list重新填充空间。
free_list节点结构如下:
union Obj
union Obj* _freeListLink; // 指向下一个内存块的指针
char _clientData[1]; /* The client sees this.*/
;</span>
空间配置函数 allocate()
1、先判断区块大小,大于128bytes直接调用一级空间配置器,小于128bytes就检查对应的free list。
2、如果free list 内有可用的区块,就直接用;如果没有可用的区块,就将区块大小上调至8的整数倍,然后调用Refill(),准备为free list重新填充空间。
template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Allocate(size_t n)
//
// 若 n > __MAX_BYTES则直接在一级配置器中获取
// 否则在二级配置器中获取
//
if (n > __MAX_BYTES)
return MallocAlloc::Allocate(n);
size_t index = FREELIST_INDEX(n);
void* ret = NULL;
//
// 1.如果自由链表中没有内存则通过Refill进行填充
// 2.如果自由链表中有则直接返回一个节点块内存
// ps:多线程环境需要考虑加锁
//
Obj* head = _freeList[index];
if (head == NULL)
return Refill(ROUND_UP(n));
else
_freeList[index] = head->_freeListLink;
return head;
空间释放函数 deallocate()
判断区块大小,大于128bytes直接调用一级空间配置器,小于128bytes,找出对应的free list,将区块回收。
template <bool threads, int inst>
void __DefaultAllocTemplate<threads, inst>::Deallocate(void *p, size_t n)
//
// 若 n > __MAX_BYTES则直接归还给一级配置器
// 否则在放回二级配置器的自由链表
//
if (n > __MAX_BYTES)
MallocAlloc::Deallocate(p, n);
else
// ps:多线程环境需要考虑加锁
size_t index = FREELIST_INDEX(n);
// 头插回自由链表
Obj* tmp = (Obj*)p;
tmp->_freeListLink = _freeList[index];
_freeList[index] = tmp;
重新填充free list
空间配置函数allocate()发现free list没有可用区块时,调用Refill()为free list重新填充空间,新的空间取自内存池。
template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Refill(size_t n)
//
// 分配20个n bytes的内存
// 如果不够则能分配多少分配多少
//
int nobjs = 20;
//调用ChunkAlloc,尝试取得nobjs个区块作为free list的新节点
char* chunk = ChunkAlloc(n, nobjs);
// 如果只分配到一块,则直接返回这块内存。
if (nobjs == 1)
return chunk;
Obj* result, *cur;
size_t index = FREELIST_INDEX(n);
result = (Obj*)chunk;
// 把剩余的块链接到自由链表上面
cur = (Obj*)(chunk + n);
_freeList[index] = cur;
for (int i = 2; i < nobjs; ++i)
cur->_freeListLink = (Obj*)(chunk + n*i);
cur = cur->_freeListLink;
cur->_freeListLink = NULL;
return result;
</span>
内存池(memory pool)
从内存池取空间给free list用,ChunkAlloc()。
//从内存池取空间给free list用
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::ChunkAlloc(size_t size, int &nobjs)
__TRACE_D
char* result;
size_t bytesNeed = size*nobjs;
size_t bytesLeft = _endFree - _startFree;//内存池剩余空间
//
// 1.内存池中的内存足够,bytesLeft>=bytesNeed,则直接从内存池中取。
// 2.内存池中的内存不足,但是够一个bytesLeft >= size,则直接取能够取出来。
// 3.内存池中的内存不足,则从系统堆分配大块内存到内存池中。
//
if (bytesLeft >= bytesNeed)
result = _startFree;
_startFree += bytesNeed;
else if (bytesLeft >= size)
result = _startFree;
nobjs = bytesLeft / size;
_startFree += nobjs*size;
else
// 若内存池中还有小块剩余内存,则将它头插到合适的自由链表
if (bytesLeft > 0)
size_t index = FREELIST_INDEX(bytesLeft);
((Obj*)_startFree)->_freeListLink = _freeList[index];
_freeList[index] = (Obj*)_startFree;
_startFree = NULL;
// 从系统堆分配两倍+已分配的heapSize/8的内存到内存池中
size_t bytesToGet = 2 * bytesNeed + ROUND_UP(_heapSize >> 4);
_startFree = (char*)malloc(bytesToGet);
//
// 【无奈之举】
// 如果在系统堆中内存分配失败,则尝试到自由链表中更大的节点中分配
//
if (_startFree == NULL)
for (int i = size; i <= __MAX_BYTES; i += __ALIGN)
Obj* head = _freeList[FREELIST_INDEX(size)];
if (head)//free list 有未用区块
//调整free list,释放未用区块
_startFree = (char*)head;
_freeList[FREELIST_INDEX(size)] = head->_freeListLink;
_endFree = _startFree + i;
//递归调用自己,为了修正nobjs
return ChunkAlloc(size, nobjs);
//
// 【最后一根稻草】
// 自由链表中也没有分配到内存,则再到一级配置器中分配内存,
// 一级配置器中可能有设置的处理内存,或许能分配到内存。
//
_startFree = (char*)MallocAlloc::Allocate(bytesToGet);
// 从系统堆分配的总字节数。(可用于下次分配时进行调节)
_heapSize += bytesToGet;
_endFree = _startFree + bytesToGet;
// 递归调用获取内存
return ChunkAlloc(size, nobjs);
return result;
// 二级空间配置器
//
template <bool threads, int inst>
class __DefaultAllocTemplate
public:
enum __ALIGN = 8 ; // 排列基准值(也是排列间隔)
enum __MAX_BYTES = 128 ; // 最大值
enum __NFREELISTS = __MAX_BYTES / __ALIGN ; // 排列链大小
static size_t ROUND_UP(size_t bytes)
// 对齐 将bytes上调至8的整数倍
return ((bytes + __ALIGN - 1) & ~(__ALIGN - 1));
//根据bytes大小,决定使用第n号free_list, n从0开始
static size_t FREELIST_INDEX(size_t bytes)
// bytes == 9 (9+7)/8-1=1;
// bytes == 8
// bytes == 7
return ((bytes + __ALIGN - 1) / __ALIGN - 1);
union Obj
union Obj* _freeListLink; // 指向下一个内存块的指针
char _clientData[1]; /* The client sees this.*/
;
static Obj* volatile _freeList[__NFREELISTS]; //16个自由链表
static char* _startFree; // 内存池水位线开始
static char* _endFree; // 内存池水位线结束
static size_t _heapSize; // 从系统堆分配的总大小
// 获取大块内存插入到自由链表中
static void* Refill(size_t n);
// 从内存池中分配大块内存
static char* ChunkAlloc(size_t size, int &nobjs);
static void * Allocate(size_t n);
static void Deallocate(void *p, size_t n);
static void* Reallocate(void *p, size_t old_sz, size_t new_sz);
;
// 初始化全局静态对象
template <bool threads, int inst>
typename __DefaultAllocTemplate<threads, inst>::Obj* volatile __DefaultAllocTemplate<threads, inst>::_freeList[__DefaultAllocTemplate<threads, inst>::__NFREELISTS];
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_startFree = 0;
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_endFree = 0;
template <bool threads, int inst>
size_t __DefaultAllocTemplate<threads, inst>::_heapSize = 0;;
template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Refill(size_t n)
__TRACE_DEBUG("(n:%u)\\n", n);
//
// 分配20个n bytes的内存
// 如果不够则能分配多少分配多少
//
int nobjs = 20;
//调用ChunkAlloc,尝试取得nobjs个区块作为free list的新节点
char* chunk = ChunkAlloc(n, nobjs);
// 如果只分配到一块,则直接返回这块内存。
if (nobjs == 1)
return chunk;
Obj* result, *cur;
size_t index = FREELIST_INDEX(n);
result = (Obj*)chunk;
// 把剩余的块链接到自由链表上面
cur = (Obj*)(chunk + n);
_freeList[index] = cur;
for (int i = 2; i < nobjs; ++i)
cur->_freeListLink = (Obj*)(chunk + n*i);
cur = cur->_freeListLink;
cur->_freeListLink = NULL;
return result;
//从内存池取空间给free list用
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::ChunkAlloc(size_t size, int &nobjs)
__TRACE_DEBUG("(size: %u, nobjs: %d)\\n", size, nobjs);
char* result;
size_t bytesNeed = size*nobjs;
size_t bytesLeft = _endFree - _startFree;//内存池剩余空间
//
// 1.内存池中的内存足够,bytesLeft>=bytesNeed,则直接从内存池中取。
// 2.内存池中的内存不足,但是够一个bytesLeft >= size,则直接取能够取出来。
// 3.内存池中的内存不足,则从系统堆分配大块内存到内存池中。
//
if (bytesLeft >= bytesNeed)
__TRACE_DEBUG("内存池中内存足够分配%d个对象\\n", nobjs);
result = _startFree;
_startFree += bytesNeed;
else if (bytesLeft >= size)
__TRACE_DEBUG("内存池中内存不够分配%d个对象,只能分配%d个对象\\n", nobjs, bytesLeft / size);
result = _startFree;
nobjs = bytesLeft / size;
_startFree += nobjs*size;
else
// 若内存池中还有小块剩余内存,则将它头插到合适的自由链表
if (bytesLeft > 0)
size_t index = FREELIST_INDEX(bytesLeft);
((Obj*)_startFree)->_freeListLink = _freeList[index];
_freeList[index] = (Obj*)_startFree;
_startFree = NULL;
__TRACE_DEBUG("将内存池中剩余的空间,分配给freeList[%d]\\n", index);
// 从系统堆分配两倍+已分配的heapSize/8的内存到内存池中
size_t bytesToGet = 2 * bytesNeed + ROUND_UP(_heapSize >> 4);
_startFree = (char*)malloc(bytesToGet);
__TRACE_DEBUG("内存池空间不足,系统堆分配%u bytes内存\\n", bytesToGet);
//
// 【无奈之举】
// 如果在系统堆中内存分配失败,则尝试到自由链表中更大的节点中分配
//
if (_startFree == NULL)
__TRACE_DEBUG("系统堆已无足够,无奈之下,只能到自由链表中看看\\n");
for (int i = size; i <= __MAX_BYTES; i += __ALIGN)
Obj* head = _freeList[FREELIST_INDEX(size)];
if (head)//free list 有未用区块
//调整free list,释放未用区块
_startFree = (char*)head;
_freeList[FREELIST_INDEX(size)] = head->_freeListLink;
_endFree = _startFree + i;
//递归调用自己,为了修正nobjs
return ChunkAlloc(size, nobjs);
//
// 【最后一根稻草】
// 自由链表中也没有分配到内存,则再到一级配置器中分配内存,
// 一级配置器中可能有设置的处理内存,或许能分配到内存。
//
__TRACE_DEBUG("系统堆和自由链表都已无内存,一级配置器做最后一根稻草\\n");
_startFree = (char*)MallocAlloc::Allocate(bytesToGet);
// 从系统堆分配的总字节数。(可用于下次分配时进行调节)
_heapSize += bytesToGet;
_endFree = _startFree + bytesToGet;
// 递归调用获取内存
return ChunkAlloc(size, nobjs);
return result;
template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Allocate(size_t n)
__TRACE_DEBUG("(n: %u)\\n", n);
//
// 若 n > __MAX_BYTES则直接在一级配置器中获取
// 否则在二级配置器中获取
//
if (n > __MAX_BYTES)
return MallocAlloc::Allocate(n);
size_t index = FREELIST_INDEX(n);
void* ret = NULL;
//
// 1.如果自由链表中没有内存则通过Refill进行填充
// 2.如果自由链表中有则直接返回一个节点块内存
// ps:多线程环境需要考虑加锁
//
Obj* head = _freeList[index];
if (head == NULL)
return Refill(ROUND_UP(n));
else
__TRACE_DEBUG("自由链表取内存:_freeList[%d]\\n", index);
_freeList[index] = head->_freeListLink;
return head;
template <bool threads, int inst>
void __DefaultAllocTemplate<threads, inst>::Deallocate(void *p, size_t n)
__TRACE_DEBUG("(p:%p, n: %u)\\n", p, n);
//
// 若 n > __MAX_BYTES则直接归还给一级配置器
// 否则在放回二级配置器的自由链表
//
if (n > __MAX_BYTES)
MallocAlloc::Deallocate(p, n);
else
// ps:多线程环境需要考虑加锁
size_t index = FREELIST_INDEX(n);
// 头插回自由链表
Obj* tmp = (Obj*)p;
tmp->_freeListLink = _freeList[index];
_freeList[index] = tmp;
template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::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 (ROUND_UP(old_sz) == 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;
typedef __DefaultAllocTemplate<false, 0> Alloc;
#endif // __USE_MALLOC
__USE_MALLOC宏将alloc定义为一级空间配置器。
以上是关于STL空间配置器的主要内容,如果未能解决你的问题,请参考以下文章