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空间配置器的主要内容,如果未能解决你的问题,请参考以下文章

STL空间配置器

STL空间配置器

STL源码剖析 — 空间配置器(allocator)

SGI STL 空间配置器

STL实现简单的空间配置器

STL实现简单的空间配置器