c++ STL(六 空間配置器、內存配置器)

一、概述

以STL運用的角度而言,空間配置器是最不須要介紹的,它老是藏在一切組件的背後,默默工做。整個STL的操做對象都存放在容器之中(vertor、list),而容器必定須要配置空間以放置資料,這就是空間配置器的做用。c++

雖然STL提供了讓咱們自定義空間配置器的接口,可是不建議本身定義,由於標準提供的空間配置器是安全的,且效率也不錯的。因此咱們使用時,通常都會使用默認的配置器。以下:數組

template <class T, class Alloc = allocator<T> >
class vector {};

vect<int> vec;	//這裏只傳入int類型,使用默認的空間配置器
  • 1
  • 2
  • 3
  • 4

下面的空間配置器是按照SGI 版本的STL進行講解的,可是STL的原理是通的。安全

二、空間配置器的內存分配和釋放

經過前面整理C++ new和delete的詳解,咱們知道C++內存配置操做和釋放操做是這樣的:函數

class Foo {...};
Foo* pf = new Foo;	//配置內存,而後構造對象
delete pf;			//將對象析構,而後釋放內存
  • 1
  • 2
  • 3

這其中的 new 內含兩個階段操做:一、調用operator new 配置內存。二、調用構造函數,構造對象內容
delete也內含兩個階段操做:一、調用析構函數。二、調用operator delete 釋放內存。性能

爲了精密分工,STL 將這兩個階段操做區分開來。內存配置操做由 成員函數 alloccate() 負責,內存釋放由 deallcate() 負責;對象構造由 construct() 負責,對象析構則由 destroy() 負責。spa

在內存分配的過程當中,會有幾個問題須要考慮。
一、小塊內存帶來的內存碎片問題。
二、小塊內存頻繁申請釋放帶來的性能問題。.net

爲了解決這些問題,SGI STL設計了 雙層級配置器,也就是第一級配置器和第二級配置器。第一級配置器直接使用 malloc() 和 free() ,第二級配置器則視狀況採用不一樣的策略:當配置區塊超過128 bytes 時,視之爲 「足夠大」,便調用第一級配置器;當配置區塊小於 128 bytes 時,視之爲 「太小」 ,爲了下降額外負擔,便採用複雜的 內存池 管理方式。設計

三、第一級配置器

第一級配置器的流程以下:指針

在這裏插入圖片描述
SGI的第一級配置器以 malloc(), free(), realloc() 等C函數執行實際的內存配置、釋放、重配置操做。當 malloc 或者 realloc 調用不成功後,改調用 oom_malloc() 和 oom_realloc() 。後二者都有內循環,不斷調用「內存不足處理例程」,指望在某次調用以後,得到足夠的內存。但若是「內存不足處理例程」未被客戶端設定,則直接拋出 bad_alloc 異常,或者終止程序。code

注意:設計內存不足處理例程是客戶端的責任,設定內存不足處理例程也是客戶端的責任。

四、第二級配置器

二級配置器使用內存池+自由鏈表的形式避免了小塊內存帶來的碎片化,提升了分配的效率,提升了利用率。它是用一個16個元素的自由鏈表(free_list)來管理的,每一個位置的內存大小都是8的倍數,分別爲:八、1六、2四、3二、40、4八、5六、6四、7二、80、8八、9六、10四、1十二、120、128。

free_list的節點結構以下:

union obj
{
	union obj* free_list_link;
	char client_data[1];
};
  • 1
  • 2
  • 3
  • 4
  • 5

使用union是爲了節省內存,這樣每一個節點就不須要額外的指針。

內存池與自由數組 free_list 之間的關係以下圖所示:
在這裏插入圖片描述其中free_list的第一個元素指向 8個字節的空間,8個字節的空間我給分配了10個。free_list的最後一個元素指向128個字節的空間,此空間我給分配了4個。

free_list管理的是內存池中已經分配給 free_list 且還沒有使用的內存,若是系統想從free_list中拿一8字節內存,則直接從free_list[0]中彈出頂部第一個元素,而後頂部後移。

4.一、二級配置器內存分配

主要分爲四種狀況:
一、free_list列表中有空餘內存。若是申請3個字節的內存,則所需空間大小提高爲8的倍數,而後去 free_list 中查找相應的鏈表,若是 free_list[i] 不爲空,則返回第一個元素,而後把頭指針日後移。

二、free_list 列表中沒有空餘,但內存池不爲空。首先檢驗內存池中的大小是否是比申請的內存大,好比申請20*8的內存,若是足夠,則分配相應內存,將其中一個分配給用戶使用,其它的掛在相應的 free_list 中。若是內存池不夠大,只夠幾個內存分配,則就分配這幾個,把相應的數據返回。若是連一個都不夠則執行第三中狀況。

三、free_list列表中沒有空餘,內存池也不夠。調用malloc從新分配內存,分配時會多分配一倍的內存,把相應的內存掛到free_list下,剩餘的放到內存池中。

四、free_list列表中沒有空餘,內存池也不夠,malloc也失敗。則調用一級空間配置器,裏面會有循環處理,或者拋出異常。

4.二、二級配置器內存回收

當用戶從二級空間配置器中申請的內存被釋放時,二級空間配置器將回收的內存插入到對應的 free_list 中。其流程以下:
在這裏插入圖片描述

4.四、總結

咱們知道,引入相對複雜的空間配置器,主要源自兩點:

一、頻繁使用malloc、free開闢釋放小塊內存帶來的性能效率的低下
二、內存碎片問題,致使不連續內存不可用的浪費

引入兩層配置器幫咱們解決了以上的問題,可是也帶來一些問題:

一、內存碎片的問題,自由鏈表所掛區塊都是8的整數倍,所以當咱們須要非8倍數的區塊,每每會致使浪費。
二、咱們並無釋放內存池中的區塊。釋放須要到程序結束以後。這樣子會致使自由鏈表一直佔用內存,其它進程使用不了。

 

 

STL下的空間配置器分位兩級,他們沒有高低之分,只有一個條件,當用戶所須要的的內存大小:

1.大於128字節時,交給一級配置器處理
2.小於等於128字節時,交給二級配置器處理
 
一級配置器的實現思想:實際就是對c下的malloc,relloc,free進行了封裝,而且在其中加入了c++的異常。
咱們要了解對於當內存不足調用失敗後,內存不足處理是用戶須要解決的問題,STL不處理,只是在你沒有內存不足處理方法或有可是調用失敗後拋出異常。
 
 
二級配置器的實現思想:
二級配置器是經過內存池和空閒鏈表配合起來的一個特別精巧的思想。
空閒鏈表:它每一個節點分別維護8,16,32.。。。128字節以8的倍數的各內存大小。
它是這樣作的,首先,用戶申請內存小於128個字節,進入二級配置器程序,假如第一次用戶申請內存爲32字節,程序直接調用S_chunk_alloc,直接申請40個大小的32字節空間,一個交給客戶,另外19個交給空閒鏈表,剩下的20個交給內存池。
接下來,第二次申請空間,假設此次用戶須要64字節的空間,首先,程序檢查64字節的空閒鏈表節點是否有空閒空間,若是有,直接分配,若是沒有,就去內存池,而後檢查內存池的大小能分配多少個本身大小的節點,徹底知足,則拿出一個給用戶,剩下的19個給空閒鏈表,若是不夠,儘量分配內存池的最大個數給用戶和空閒鏈表。若是一個都分配不出,首先把內存池中的剩下沒用的小內存分給相應適合的空閒鏈表,而後繼續調用S_chunk_alloc申請內存,內存申請不足,去檢索空閒鏈表有沒有還沒有使用的大的內存塊,若是有,就拿出來給給內存池,遞歸調用S_chunk_alloc,若是空閒鏈表也沒有了,則交給一級適配器(由於其有對內存不足處理的方法)。內存夠,則拿到申請的內存補充內存池。重複上面的操做,將一個給客戶,其餘的19個交給空閒鏈表,剩下的給內存池。
 
二級配置器實現了對小內存的高效管理,使內部碎片出現的機率大大下降,我的看法,二級適配器差很少已經達到了對小內存接近完美的處理。
相關文章
相關標籤/搜索