1. 程式人生 > >STL原始碼分析之記憶體池

STL原始碼分析之記憶體池

前言

上一節只分析了第二級配置器是由多個連結串列來存放相同記憶體大小, 當沒有空間的時候就向記憶體池索取就行了, 卻沒有具體分析記憶體池是怎麼儲存空間的, 是不是記憶體池真的有用不完的記憶體, 本節我們就具體來分析一下

記憶體池

static data template的初始化

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; // 多次呼叫記憶體池, 就會更多的是給連結串列分配記憶體, 這就是一個增量.

這裡程式碼註釋寫的很清楚了, 我就提取出來分析一下吧

  1. 記憶體池的大小大於需要的空間, 直接返回起始地址(nobjs預設設定為20, 所以每次呼叫都會給連結串列額外的19個記憶體塊)
  2. 記憶體池的記憶體不足以馬上分配那麼多記憶體, 但是還能滿足分配一個即以上的大小, 那就全部分配出去
  3. 如果一個物件的大小都已經提供不了了, 先將零碎的記憶體塊給一個小記憶體的連結串列來儲存, 然後就準備呼叫malloc申請40塊+額外大小的記憶體塊(額外記憶體塊就由heap_size決定), 如果申請失敗跳轉到步驟4, 成功跳轉到步驟6
  4. 充分利用更大記憶體的連結串列, 通過遞迴來呼叫他們的記憶體塊
  5. 如果還是沒有記憶體塊, 直接呼叫一級配置器來申請記憶體, 還是失敗就丟擲異常, 成功申請就繼續執行
  6. 重新修改記憶體起始地址和結束地址為當前申請的地址塊, 重新呼叫chunk_alloc分配記憶體
// 記憶體池
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 
  	{ 
	    // 如果一個物件的大小都已經提供不了了, 那就準備呼叫malloc申請兩倍+額外大小的記憶體
	    size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
    	// Try to make use of the left-over piece.
    	// 記憶體池還剩下的零頭記憶體分給給其他能利用的連結串列, 也就是絕不浪費一點.
    	if (bytes_left > 0) 
    	{
      		// 連結串列指向申請記憶體的地址
      		obj * __VOLATILE * my_free_list = free_list + FREELIST_INDEX(bytes_left);
      		((obj *)start_free) -> free_list_link = *my_free_list;
      		*my_free_list = (obj *)start_free;
    	}
    	start_free = (char *)malloc(bytes_to_get);
    	// 記憶體不足了
    	if (0 == start_free) 
    	{
      		int i;
      		obj * __VOLATILE * my_free_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) 
	            {
	          		*my_free_list = p -> free_list_link;
          			start_free = (char *)p;
	          		end_free = start_free + i;
          			return(chunk_alloc(size, nobjs));
    	    	}
      		}
      		// 如果一點記憶體都沒有了的話, 就只有呼叫一級配置器來申請記憶體了, 並且使用者沒有設定處理例程就丟擲異常
      		end_free = 0;	// In case of exception.
      		start_free = (char *)malloc_alloc::allocate(bytes_to_get);
	    }
	    	// 申請記憶體成功後重新修改記憶體起始地址和結束地址, 重新呼叫chunk_alloc分配記憶體
	    	heap_size += bytes_to_get;
	    	end_free = start_free + bytes_to_get;
    		return(chunk_alloc(size, nobjs));
  	}	
}

chunk_alloc函式的第34行FREELIST_INDEX的實現, 他將傳入的bytes執行+7除8操作, 實現將byte調為大於byte最近8的整數.

    static  size_t FREELIST_INDEX(size_t bytes) {
      return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }

這種方法很像c程式位元組的對齊, 就像下面這樣, 從而實現位元組的對齊

#define MASK 0xffffffff
static  size_t FREELIST_INDEX(size_t bytes) {
      return (((bytes) + __ALIGN-1)/MASK);
    }

總結

記憶體池的存在就是為了能快速的提供我們做需要的記憶體並且儲存多餘的空間, 讓STL分配空間不再每次都進行malloc和free的操作, 效率又很有保障. 有時使用者申請的塊更小, 我們也能充分的利用起來. 唯一可能不足的是我們每次只申請char個大小, 但是記憶體池獲得的確是8位元組的大小. 感謝@Hall_Wood指出8位元組對齊計算的問題.