STL的二級空間配置器相似於memory_pool,可是並非memory_pool,由於STL的二級空間配置器只維護了16個free_list,並且只是小於128byte大小的小區塊,大於128byte會調用一級空間配置器malloc。16個鏈表放在數組中,每個數組所鏈的鏈表表明大小相等的小區塊,由於分配的時候,只會申請8的倍數大小的空間,因此小於128byte的區塊確定是屬於free_list裏面。當小區塊釋放時,就會掛到free_list中。數組
下面是二級空間配置器的源碼:安全
_ALIGN = 8,表示最小的小區塊的大小
_MAX_BYTES = 128 表示最大的區塊的大小
_NFREELISTS = 16 表示free_list的個數
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__)) enum {_ALIGN = 8}; enum {_MAX_BYTES = 128}; enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN # endif
這是將申請的大小按8字節對齊:
static size_t _S_round_up(size_t __bytes) { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
這是free_list的Node結構體:多線程
__PRIVATE: union _Obj { union _Obj* _M_free_list_link; char _M_client_data[1]; /* The client sees this. */ };
free_list的定義:函數
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC) static _Obj* __STL_VOLATILE _S_free_list[]; // Specifying a size results in duplicate def for 4.1 # else static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; //volatile 是爲了防止在多線程下產生不可預期的後果 # endif
內存池的相關信息:ui
static char* _S_start_free; //內存池中未分給free_pool的首地址 static char* _S_end_free; //內存池的末尾地址 static size_t _S_heap_size;//內存池的總空間大小
二級空間配置器分配函數:this
static void* allocate(size_t __n) { void* __ret = 0; //若是size大於128byte就使用一級配置器 if (__n > (size_t) _MAX_BYTES) { __ret = malloc_alloc::allocate(__n); } else { _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__n); //找到size對應的list // Acquire the lock here with a constructor call. // This ensures that it is released in exit or during stack // unwinding. # ifndef _NOTHREADS /*REFERENCED*/ _Lock __lock_instance;//有關線程安全 # endif _Obj* __RESTRICT __result = *__my_free_list; //指向size所在鏈表頭 if (__result == 0) __ret = _S_refill(_S_round_up(__n)); //當這個鏈表爲空時,從新建立一個小chunk else { *__my_free_list = __result -> _M_free_list_link; //將鏈第一個節點取下來 __ret = __result; //給用戶返回取下來的小區塊的地址 } } return __ret; };
下面的函數是從一級空間配置器(malloc)申請小塊內存的函數:線程
template <bool __threads, int __inst> void* __default_alloc_template<__threads, __inst>::_S_refill(size_t __n) { int __nobjs = 20; /*默認的建議值,在申請小內存塊的時候,一次申請的個數,這並非最終個數,會根據內存池剩下的空間大小進行調整*/ char* __chunk = _S_chunk_alloc(__n, __nobjs);//從內存池中申請_nobjs個_n大小的空間 _Obj* __STL_VOLATILE* __my_free_list; //指向free_list的指針 _Obj* __result; _Obj* __current_obj; _Obj* __next_obj; int __i; /*若是在_S_chunk_alloc中只申請到了一塊這樣大的空間,則直接返回*/ if (1 == __nobjs) return(__chunk); __my_free_list = _S_free_list + _S_freelist_index(__n); //找到該大小所對應的free_list /*將獲得的多塊_n大小的chunk,第一塊返回給用戶,剩下的加到_n所對應的free_list中*/ __result = (_Obj*)__chunk; *__my_free_list = __next_obj = (_Obj*)(__chunk + __n); for (__i = 1; ; __i++) { __current_obj = __next_obj; __next_obj = (_Obj*)((char*)__next_obj + __n); if (__nobjs - 1 == __i) { __current_obj -> _M_free_list_link = 0; break; } else { __current_obj -> _M_free_list_link = __next_obj; } } return(__result); }
_S_chunk_alloc:指針
template <bool __threads, int __inst> char* __default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, int& __nobjs) { char* __result; size_t __total_bytes = __size * __nobjs; //想要開闢的總大小 size_t __bytes_left = _S_end_free - _S_start_free; //內存池的剩餘大小 /*判斷內存池剩餘的空間和須要申請的空間的大小比較*/ if (__bytes_left >= __total_bytes) { __result = _S_start_free; //大於就能夠直接分配 _S_start_free += __total_bytes; return(__result); } else if (__bytes_left >= __size) { __nobjs = (int)(__bytes_left/__size);//調整須要申請的塊數 //使用內存池剩餘空間分配大小爲_size的內存塊,能分多少塊就分多少塊 __total_bytes = __size * __nobjs; __result = _S_start_free; _S_start_free += __total_bytes; //將內存池的start往下移,分配空間 return(__result); } else { size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4); //內存吃沒有足夠的空間分配時,須要從新申請空間,這裏的size就是須要向一級空間配置器申請的空間大小 if (__bytes_left > 0) { _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left);//將內存池剩餘的小於_size的空間掛到對應大小的free_list上。 ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list; *__my_free_list = (_Obj*)_S_start_free; } /*內存池剩餘爲0時,malloc一個小塊內存,進行分配*/ _S_start_free = (char*)malloc(__bytes_to_get); if (0 == _S_start_free) { size_t __i; _Obj* __STL_VOLATILE* __my_free_list; _Obj* __p; /*若是這是第一次申請內存池空間,則進行循環分配小塊空間*/ for (__i = __size; __i <= (size_t) _MAX_BYTES; __i += (size_t) _ALIGN) { __my_free_list = _S_free_list + _S_freelist_index(__i); __p = *__my_free_list; if (0 != __p) { *__my_free_list = __p -> _M_free_list_link; _S_start_free = (char*)__p; _S_end_free = _S_start_free + __i; return(_S_chunk_alloc(__size, __nobjs)); } } //將申請的空間分配完以後,再申請一塊內存池空間做爲備用 _S_end_free = 0; _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); } /*若是不是第一次申請內存池空間,把內存池的大小擴大,再調用本身進行空間分配*/ _S_heap_size += __bytes_to_get; _S_end_free = _S_start_free + __bytes_to_get; return(_S_chunk_alloc(__size, __nobjs)); } }
這就是整個STL庫中的二級空間配置器的流程,其實也在模仿ptmalloc的內存管理機制,使用內存池和空間回收,避免屢次庫函數調用或者系統調用,大大節省了程序運行的時間。這裏的二級空間配置器只要用到了free_list和小塊內存池,雙層保證小塊空間分配的速率比直接malloc快。可是在實戰中的效率對比仍是須要實踐才能得出結論。code