1. 程式人生 > >STL空間配置器剖析

STL空間配置器剖析

根據情況來判定,如果配置區塊大於128bytes,說明“足夠大”,呼叫第一級配置器,而小於等於128bytes,則採用複雜記憶體池(memory pool)來管理。


template <bool threads, int inst>  
class __default_alloc_template 
{  
	private:  
		// 實際上我們應該使用 static const int x = N  
		// 來取代 enum { x = N }, 但目前支援該性質的編譯器還不多。  
		# ifndef __SUNPRO_CC  
		enum {__ALIGN = 8};  
		enum {__MAX_BYTES = 128};  
		enum {__NFREELISTS = __MAX_BYTES/__ALIGN};  
		# endif  

		//注意此處的位運算技巧,將bytes上調至8的倍數
		static size_t ROUND_UP(size_t bytes) 
		{  
			return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));  
		}  

		//注意此處的union技巧
		union obj 
		{  
			union obj* free_list_link;  
			char client_data[1];    /* The client sees this. */  
		};  

	private:  
# ifdef __SUNPRO_CC  
		static obj * __VOLATILE free_list[];   
		// Specifying a size results in duplicate def for 4.1  
# else  
		static obj * __VOLATILE free_list[__NFREELISTS];   
# endif  
		static size_t FREELIST_INDEX(size_t bytes) 
		{  
			return (((bytes) + __ALIGN - 1) / __ALIGN - 1);  
		}  

		// 傳回一個大小為n的物件,並可能加入大小為n的其他區塊到free list
		static void *refill(size_t n);  

		// 配置一大塊空間,可容納nobjs個大小為“size”的區塊 
		// 如果配置nobjs個區塊有所不便,nobjs可能會降低  
		static char *chunk_alloc(size_t size, int &nobjs);  

		// Chunk allocation state.  
		static char *start_free;	// 記憶池起始位置,只在chunk_alloc()中變化
		static char *end_free;	// 記憶池結束位置,只在chunk_alloc()中變化
		static size_t heap_size;  

		// 判斷區塊大小,大於128bytes就呼叫第一級配置器,小於128bytes就檢查相應的free list。
		// 如果free list之內有可用的區塊,就直接拿來用。如果沒有可用的區塊,就將區塊大小上調至8的倍數,然後呼叫refill
		/* n must be > 0 */
		static void * allocate(size_t n)
		{
			obj *volatile *my_free_list;
			obj *result;
			
			// 大於128bytes就呼叫第一級配置器
			if (n > (size_t)__MAX_BYTES)
			{
				return (malloc_alloc::allocate(n));
			}
			// 否則,在free_list尋找合適的一個
			my_free_list = free_list + FREELIST_INDEX(n);
			result = *my_free_list;
			if (NULL == result)
			{
				void *r = refill(ROUND_UP(n));// 如果沒有合適的,就將n上調至8的倍數
				return r;
			}
			
			// 調整free list
			*my_free_list = result->free_list_link;
			return result;
		}  

		// n大於128bytes就呼叫第一級配置器,小於128bytes就找出對應的free list將區塊回收
		/* p may not be 0 */  
		static void deallocate(void *p, size_t n)
		{
			obj *q = (obj*)p;;
			obj * volatile * my_free_list;
			
			// 大於128bytes就呼叫第一級配置器
			if (n > (size_t)__MAX_BYTES)
			{
				malloc_alloc::deallocate(p,n);
				return;
			}
			// 否則,尋找對應的free list
			my_free_list = free_list + FREELIST_INDEX(n);
			
			// 調整free list,回收區塊
			q->free_list_link = *my_free_list;
			*my_free_list = q;
		}  
		static void * reallocate(void *p, size_t old_sz, size_t new_sz);  
		
		// 以下是靜態資料成員的定義和初始化
		template <bool threads, int inst>  
		char *__default_alloc_template<threads, inst>::start_free = 0;//記憶體池起始位置  

		template <bool threads, int inst>  
		char *__default_alloc_template<threads, inst>::end_free = 0;//記憶體池結束位置  

		template <bool threads, int inst>  
		size_t __default_alloc_template<threads, inst>::heap_size = 0;  

		template <bool threads, int inst>  
		__default_alloc_template<threads, inst>::obj * __VOLATILE  
		__default_alloc_template<threads, inst> ::free_list[  
		# ifdef __SUNPRO_CC  
			__NFREELISTS  
		# else  
			__default_alloc_template<threads, inst>::__NFREELISTS  
		# endif  
		] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; 
};


// 幾個呼叫的全域函式

// refill(),傳回一個大小為n的物件,並且有時候會為適當的free list增加節點
// 假設n已經適當上調至8的倍數
template <bool threads,int inst>
void * __default_alloc_template<threads,inst>::refill(size_t n)
{
	int nobjs = 20;
	// 呼叫chunk_alloc(),嘗試獲得nobjs個區塊作為free list的新節點
	char * chunk = chunk_alloc(n,nobjs);
	obj * volatile *my_free_list;
	obj * result;
	obj * current_obj,*next_obj;
	int i;
	
	// 如果只獲得一個區塊,這個區塊就撥給呼叫者用,free list無新節點
	if (1 == nobjs)
	{
		return chunk;
	}
	
	// 否則準備調整free list,納入新節點
	my_free_list = free_list + FREELIST_INDEX(n);
	
	// 以下在chunk空間建立free list
	result = (obj*)chunk;	// 這一塊準備傳回給客端
	
	// 以下導引free list指向新配置的空間(取自記憶池)
	*my_free_list = next_obj = (obj*)(chunk + n);
	
	// 以下將free list的各節點串接起來
	for (i = 1; ; i++)	// 從1開始,因為第0個傳回給客端
	{
		current_obj = next_obj;
		next_obj = (obj*)((char*)next_obj + n);
		if (nobjs - 1 == i)
		{
			current_obj->free_list_link = 0;
			break;
		}
		else
		{
			current_obj->free_list_link = next_obj;
		}
	}
	
	return result;
}


// 假設size已經適當上調至8的倍數
template <bool threads,int inst>
char * __default_alloc_template<threads,inst>::chunk_alloc(size_t size,int& nobjs)
{
	char *result;
	size_t total_bytes = size * nobjs;
	size_t bytes_left = end_free - start_free;	// 記憶池剩餘空間
	
	if (bytes_left >= total_bytes)
	{
		// 記憶池剩餘空間完全滿足需求量
		result = start_free;
		start_free += total_bytes;
		return result;
	}
	else if (bytes_left >= size)
	{
		// 記憶池剩餘空間不能滿足需求,但足夠提供一個或以上的區塊
		nobjs = bytes_left / size;
		total_bytes = size * nobjs;
		result = start_free;
		start_free += total_bytes;
		return result;
	}
	else
	{
		// 記憶池剩餘空間連一個區塊大小都無法提供
		size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
		
		// 以下試著讓記憶池中的殘餘零頭還有利用價值
		if (bytes_left > 0)
		{
			// 首先尋找合適的free list
			obj* volatile* my_free_list = free_list + FREELIST_INDEX(bytes_left);
			
			// 調整free list,將記憶池中的殘餘空間編入
			((obj*)start_free)->free_list_link = *my_free_list;
			*my_free_list = (obj*)start_free;
		}
		
		// 配置heap空間
		start_free = (char *)malloc(bytes_to_get);
		if (NULL == start_free)
		{
			// heap空間不足,malloc失敗
			int i;
			obj* volatile* my_fress_list,*p;
			for (i = size;i <= __MAX_BYTES;i += __ALIGN)
			{
				my_free_list = free_list + FREELIST_INDEX(i);
				p = *my_free_list;
				if (0 != p)	// free list內尚有未用區塊
				{
					// 調整free list以釋放出未用區塊
					*my_free_list = p->free_list_link;
					start_free = (char*)p;
					end_free = start_free + i;
					// 遞迴呼叫自己,為了修正nobjs
					return (chunk_alloc(size,nobjs));
					// 注意,任何殘餘零頭終將會被編入適當的free list中備用
				}
			}
			end_free = 0;
			// 呼叫第一級配置器,看看oom機制能否盡點力
			start_free = (char*)malloc_alloc::allocate(bytes_to_get);
		}
		heap_size += bytes_to_get;
		end_free = start_free + bytes_to_get;
		// 遞迴呼叫自己,為了修正nobjs
		return (chunk_alloc(size,nobjs));
	}
}
        obj * volatile * my_free_list這個宣告原來的形式是:obj** my_free_list,這樣的話*my_free_list(空閒的記憶體塊指標陣列中的一個元素)可能被優化到暫存器中,從而使庫程式碼無法lock住對它的讀呼叫(如果在暫存器中則另一個執行緒可能會無意中修改該暫存器的值,而在記憶體中由於另一個執行緒沒有訪問權力所以不能修改)。        要宣告變數必須在記憶體中就要用volatile修飾,這裡修飾的是*my_free_list,是free_list陣列中的一個元素,而不是陣列指標,所以volatile放在兩個*中間。
來源:https://blog.csdn.net/syyon123456789/article/details/51495740
(1)allocate函式分析