STL源碼剖析之空間配置器

本文大體對STL中的空間配置器進行一個簡單的講解,因爲只是一篇博客類型的文章,沒法將源碼錶現到面面俱到,因此真正感興趣的碼農們能夠從源碼中或者《STL源碼剖析》仔細瞭解一下。數組

1,爲何STL要專門寫一個空間配置器管理空間的分配和釋放,不能直接使用Malloc嗎?數據結構

⑴ 咱們若是頻繁的進行一些小空間的申請與釋放,加入先申請10個字節的空間,而後每隔一個4字節,將其釋放,那後面若是要再次申請比4個字節大的空間,那毫無疑問,前面已經被釋放的是沒法使用的。-》內存碎片問題架構

⑵ 咱們都知道malloc是系統調用,頻繁使用會無疑會使咱們的程序性能降低許多,因此咱們能夠經過申請一塊大的內存,本身進行管理,那無疑會使得malloc調用的次數下降。-》性能問題函數

對於內存碎片的問題若是不太理解,看一下這個圖立馬就懂(盜圖)性能

 

2,咱們知道了緣由,那STL是如何設計空間配置器並避免以上問題?spa

⑴ STL將空間配置器分爲1、二級,若是用戶申請空間是大於128個字節,則利用一級空間配置器使用系統調用malloc分配空間,若是小於128字節,則利用二級空間配置器進行內存管理分配。(此處應有源碼)設計

template<class T, class Alloc>指針

class simple_alloc{blog

pubulic:遞歸

  static T *allocate(size_t n)

  { return 0 == n? 0 : (T*)Alloc::allocate(n* sizeof(T)); }

  ...

}

這個類中一共實現了4個方法,2個分配,2個釋放(參數不一樣而已),咱們若是看源碼,能夠看到STL中申請或釋放內存均是使用simple_alloc的方法,其實就是作一個封裝,最主要的調用方法仍是類型Alloc,再往底層看Alloc是怎麼來的:

template<class T, class Alloc = alloc>

class vector{

typedef simple_alloc<T, Alloc> data_alloctor;

data_alloctor::allocate(n);

...

}

上段代碼就已經說明了simple_alloc的使用,那還有一個問題alloc又是怎麼來的,咱們怎麼控制這裏調用的是以及仍是二級呢?來,繼續(封裝好累啊。。。)

#ifdef __USE_MALLOC

...

typedef __malloc_alloc_template<0> malloc_alloc;

typedef malloc_alloc alloc;  //使用alloc時候就爲一級配置器

#else

...

typedef __default_alloc_template<_NODE_ALLOCATOR_THREADS,0> alloc;  //使用alloc的時候就爲二級配置器

#endif

能夠經過源碼看到咱們是經過__USE_MALLOC控制是否開啓二級配置器的功能,STL中__USE_MALLOC的值默認是爲FALSE的,因此會優先使用二級配置器,若是申請內存大小在二級配置器管理的List中沒有找到,仍是會調用一級配置器的申請方法。

 

⑵ 二級空間配置器的組織架構就是一個16個元素的自由鏈表管理,每個塊下面都掛着各自管理大小的內存(默認是以8爲位數的內存塊,8,16,24,32...128字節)。每一個節點的結構都是一個共用體:

union obj

{

  union ogj* free_list_link;

  char client_data[1];

}

這裏使用共用體的緣由主要是由於管理內存的數據結構是一個鏈表,指向下一個節點的指針有可能會成爲內存的負擔,使用共用體後free_list_link既能夠指向相同形式的另外一個obj,一樣這個指針也能夠指向實際的內存塊(這句話不是特別理解,有點亂)。下一個圖應該就能夠了解了:

從圖中就能夠看到free_list能夠經過指針相連,下方須要管理的內存塊也能夠經過指針相連,此處指針會指向還未被使用的內存地址,以便於管理。具體的存放過程來看看下圖:

在空間沒有分配出去,當前my_free_list指向的是當前96字節內存管理塊最開始的位置,須要時,直接將這塊內存分配出去,放出須要的值n,而後my_free_list直接指向下一個節點就好。而後釋放是一樣的:

只要將my_free_list指針指向當前釋放空間,而後將空間與後面的塊連起來。(my_free_list整個操做徹底是鏈表操做,只看圖的確像數組,但若是是數組咱們就很難對申請和釋放進行快速管理)。

 

3,理論架構講完,是不是要看一下代碼了?

static void * allocate(size_t n)

{

  obj * volatile * my_free_list;

  obj * result;

  //先判斷是否大於128字節

  if(n > (size_t) __MAX_BYTES)  // enum {   __MAX_BYTES   =   128};

  {

    return (malloc_alloc::allocate(n));  //調用一級空間配置器

  }

  //根據大小尋找適合的內存塊

  my_free_list = free_list + FREELIST_INDEX(n);      //函數實現爲 return ((n + __ALIGN - 1) / __ALIGN - 1 );     __ALIGN 是 8

  result = *my_free_list;

  if(result == 0)

  {

    //沒有找到可用的內存塊,準備從新填充

    void *r = refill(ROUND_UP(n));  //函數實現爲 return ((n + __ALIGN - 1) & ~( __ALIGN - 1) );    實際意義是取整,好比是1,2,3,4,5,6,7   均取 8

    return r;

  }

  *my_free_list = result -> free_list_link;  //須要分出去一塊,因此指向下一塊

  return result;

}

上述爲分配空間的函數:先比較大小,大於128字節就是用一級空間配置,不然是用二級配置。而後再管理列表裏面找內存,沒有可以使用的就須要進行從新填充(後面會說,主要原理就是有內存就再次申請一塊,沒有就須要調整一些沒有用到的大內存塊,好比128字節不經常使用,就拉過來用);若是有直接可以使用的直接改變指針的指向,返回當前塊就OK。下面再來看看釋放空間的函數。

static void deallocate(void *p, size_t n)

{

  obj *q = (obj*) p;

  boj * volatile * my_free_list;

  //一樣,大於128字節使用一級控件配置器

  if(n > (size_t)__MAX_BYTES)

  {

    malloc_alloc::deallocate(p, n);

    return;

  }

  my_free_list = free_list + FREELIST_INDEX(n);

  q -> free_list_link = *my_free_list;

   *my_free_list = q;

}

上述就是空間配置器最重要的二級配置器的申請和釋放的過程,看完是否是感受很簡單,固然,真正運做起來還須要一系列的輔佐函數,好比填充函數refill的實現。

template <bool threads, int inst>

void * __default_alloc_template<threads, inst>::refill(size_t n)

{
  int nobjs = 20;  //默認是填充當前要申請的20個內存塊大小的內存

  char * chunk = chunk_alloc(n , nobjs);  //chunk_alloc函數能夠將新申請的空間做爲free_list的新節點(做用仍是蠻多的)

  obj * volatile * my_free_list;

  obj * result;

  obj * current_obj, * next_boj;

  int i;

 

  if(nobjs  == 1)  return chunk; //我的以爲有點多餘...,首先nobjs是一個常量,默認20,並且在chunk_alloc函數傳進去的參數也不是&傳遞,因此不會修改。

  my_free_list = free_list + FREELIST_INDEX(n);

  //新開闢的空間須要按照咱們的規則鏈起來

  result = (obj *)chunk;

  *my_free_list = next_obj = (obj*)(chunk + n);

  for(i = 1; ;i++) //經過一個For循環,將整個大塊分紅20個小塊,而後相互串聯起來

  { ... }

  return result;

}

chunk_alloc函數是空間配置器中的內存池函數,主要內容就是先判斷內存池中是否有空間,有的話直接將某一段地址空間返回就OK了,若是大小不夠提供當前申請的塊數(20塊),就會有多少給多少。固然若是內存不夠不會直接返回,會進一步調用malloc進行申請空間,補充內存池的不足,若是連堆中內存也沒有空餘的了,那就必須對當前free_list進行碎片整理,好比須要大內存塊,就將小內存塊進行整合,若是須要小內存塊,則會將最大內存塊進行分解,這裏會採用一個遞歸調用自身來進行處理,固然若是連自身也沒有可用塊,那就是真的山區窮水盡了,就會交給一級空間配置器,一級空間配置有響應的new_handler機制,若是申請不到控件,會返回異常。具體源碼有點多,也比較麻煩,大體的過程就是上述內容,想要仔細瞭解的能夠參考源碼剖析。

上述全部就是STL中空間配置器最重要也是最麻煩的二級空間配置器的源碼和總體的工做流程,至於一級空間配置器呢,主要經過系統調用malloc/realloc等函數,詳細就不在此說明了,有興趣能夠自行了解。

 

4,STL之每篇一坑?

STL控件配置器有坑嗎?那確定是有的:

1,最重要的就是空間回收問題,若是有仔細研究過源碼的人,必定發現咱們若是使用二級空間配置器所開闢出來的空間,若是掛在free_list上以後,使用完了調用deallocate函數,僅僅只能講空間的管理權還給free_list而已,它但是沒有還給真正的堆啊,而後再仔細過一下釋放空間的流程,發現它徹底沒有釋放給堆啊。。。也就說當咱們使用了大量內存以後,釋放並無真正將空間釋放出去,而是掛在了free_list上面,這是否是很坑啊?

2,還有一點較爲坑的就是二級空間配置器每塊內存都是8的倍數,也就是否是8的倍數時補全是會浪費部分空間的。

但整體來說,STL的一個效率和適用性仍是很強的,它無疑是儘量的減小了內存碎片的產生,相比一個項目,咱們不斷的去開闢和釋放空間,是很是容易形成碎片產生,而STL有效的防止了這一點。

 

 本章主要就寫這麼多,網上有看過一些這方面的博客,整體來講本文已經很詳細了。

相關文章
相關標籤/搜索