STL中的空间配置器
STL中的空間配置器
文章目錄
- STL中的空間配置器
- 1. 什么是空間配置器
- 2. 為什么需要空間配置器
- 3. SGI-STL空間配置器實現(xiàn)原理
- 3.1 一級空間配置器
- 3.2 二級空間配置器
- 3.1 內(nèi)存池
- 3.2 SGI-STL中二級空間配置器設(shè)計
- 3.3 SGI-STL二級空間配置器之空間申請
- 3.3 空間配置器的默認選擇
- 3.4 空間配置器的再次封裝
- 3.5 對象的構(gòu)造與釋放
- 4. 與容器結(jié)合
- 5.總結(jié)
1. 什么是空間配置器
空間配置器,顧名思義就是為各個容器高效的管理空間(空間的申請與回收)的,在默默地工作。
2. 為什么需要空間配置器
在模擬實現(xiàn)vector、list、map、unordered_map等容器時,所有需要空間的地方都是通過new申請 的,雖然代碼可以正常運行,但是有以下不足之處:
- 空間申請與釋放需要用戶自己管理,容易造成內(nèi)存泄漏
- 頻繁向系統(tǒng)申請小塊內(nèi)存塊,容易造成內(nèi)存碎片
- 頻繁向系統(tǒng)申請小塊內(nèi)存,影響程序運行效率
- 直接使用malloc與new進行申請,每塊空間前有額外空間浪費
- 申請空間失敗怎么應(yīng)對
- 代碼結(jié)構(gòu)比較混亂,代碼復用率不高
- 未考慮線程安全問題
因此需要設(shè)計一塊高效的內(nèi)存管理機制。
3. SGI-STL空間配置器實現(xiàn)原理
- 以上提到的幾點不足之處,最主要還是:頻繁向系統(tǒng)申請小塊內(nèi)存造成的。
- 那什么才算是小塊內(nèi)存?SGI-STL 以128作為小塊內(nèi)存與大塊內(nèi)存的分界線
- 將空間配置器其分為兩級結(jié)構(gòu),一級空間配置器處理大塊內(nèi)存, 二級空間配置器處理小塊內(nèi)存。
3.1 一級空間配置器
一級空間配置器主要針對大于128字節(jié)的空間,原理非常簡單,直接對malloc與free進行了封裝,并增加了C++中set_new_handle思想。
template <int inst> class __malloc_alloc_template { private:static void *oom_malloc(size_t);public:// 對malloc的封裝static void * allocate(size_t n){// 申請空間成功,直接返回,失敗交由oom_malloc處理void *result = malloc(n);if (0 == result)result = oom_malloc(n);return result;}// 對free的封裝static void deallocate(void *p, size_t /* n */){ free(p);}// 模擬set_new_handle// 該函數(shù)的參數(shù)為函數(shù)指針,返回值類型也為函數(shù)指針// void (* set_malloc_handler( void (*f)() ) )()static void (* set_malloc_handler(void (*f)()))(){void (* old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = f;return(old);} };// malloc申請空間失敗時代用該函數(shù)template <int inst>void * __malloc_alloc_template<inst>::oom_malloc(size_t n){void (* my_malloc_handler)();void *result;for (;;){// 檢測用戶是否設(shè)置空間不足應(yīng)對措施,如果沒有設(shè)置,拋異常,模式new的方式my_malloc_handler = __malloc_alloc_oom_handler;if (0 == my_malloc_handler){__THROW_BAD_ALLOC;}// 如果設(shè)置,執(zhí)行用戶提供的空間不足應(yīng)對措施(*my_malloc_handler)();// 繼續(xù)申請空間,可能就會申請成功 result = malloc(n);if (result)return(result);}} typedef __malloc_alloc_template<0> malloc_alloc;3.2 二級空間配置器
二級空間配置器專門負責處理小于128字節(jié)的小塊內(nèi)存。如何才能提升小塊內(nèi)存的申請與釋放的方式呢?SGI- STL采用了內(nèi)存池的技術(shù)來提高申請空間的速度以及減少額外空間的浪費,采用哈希桶的方式來提高用戶獲 取空間的速度與高效管理。
3.1 內(nèi)存池
- 內(nèi)存池就是:先申請一塊比較大的內(nèi)存塊已做備用,當需要內(nèi)存時,直接到內(nèi)存池中去去,當池中空間不夠時,再向內(nèi)存中去取,當用戶不用時,直接還回內(nèi)存池即可。
- 避免了頻繁向系統(tǒng)申請小塊內(nèi)存所造成的效率低、內(nèi)存碎片以及額外浪費的問題
3.2 SGI-STL中二級空間配置器設(shè)計
- SGI-STL中的二級空間配置器使用了內(nèi)存池技術(shù),但沒有采用鏈表的方式對用戶已經(jīng)歸還的空間進行管理(因為用戶申請空間時在查找合適的小塊內(nèi)存時效率比較低)
- 而是采用了哈希桶的方式進行管理。
- 那是否需要 128桶個空間來管理用戶已經(jīng)歸還的內(nèi)存塊呢?答案是不需要,因為用戶申請的空間基本都是4的整數(shù)倍,其 他大小的空間幾乎很少用到。因此:SGI-STL將用戶申請的內(nèi)存塊向上對齊到了8的整數(shù)倍
3.3 SGI-STL二級空間配置器之空間申請
1. 前期的準備
// 去掉代碼中繁瑣的部分 template <int inst> class __default_alloc_template { private:enum {__ALIGN = 8}; // 如果用戶所需內(nèi)存不是8的整數(shù)倍,向上對齊到8的整數(shù)倍enum {__MAX_BYTES = 128}; // 大小內(nèi)存塊的分界線enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // 采用哈希桶保存小塊內(nèi)存時所需桶的個數(shù)// 如果用戶所需內(nèi)存塊不是8的整數(shù)倍,向上對齊到8的整數(shù)倍static size_t ROUND_UP(size_t bytes){return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));} private:// 用聯(lián)合體來維護鏈表結(jié)構(gòu)union obj{union obj * free_list_link;char client_data[1]; /* The client sees this. */}; private:static obj * free_list[__NFREELISTS];// 哈希函數(shù),根據(jù)用戶提供字節(jié)數(shù)找到對應(yīng)的桶號static size_t FREELIST_INDEX(size_t bytes){return (((bytes) + __ALIGN-1)/__ALIGN - 1);}// start_free與end_free用來標記內(nèi)存池中大塊內(nèi)存的起始與末尾位置static char *start_free;static char *end_free;// 用來記錄該空間配置器已經(jīng)想系統(tǒng)索要了多少的內(nèi)存塊static size_t heap_size;// ... } ;2. 申請空間
3. 填充內(nèi)存塊
4. 向內(nèi)存池中索要空間
3.4 SGI-STL二級空間配置器之空間回收
3.3 空間配置器的默認選擇
SGI-STL默認使用一級還是二級空間配置器,通過USE_MALLOC宏進行控制:
#ifdef __USE_MALLOCtypedef malloc_alloc alloc;typedef malloc_alloc single_client_alloc; #else// 二級空間配置器定義 #endif在SGI_STL中該宏沒有定義,因此:默認情況下SGI_STL使用二級空間配置器
3.4 空間配置器的再次封裝
在C++中,用戶所需空間可能是任意類型的,有單個對象空間,有連續(xù)空間,每次讓用戶自己計算所需空間總大小不是很友好,因此SGI-STL將空間配置器重新再封裝了一層
// T: 元素類型 // Alloc: 空間配置器 // 注意:該類只負責申請與歸還對象的空間,不否則空間中對象的構(gòu)造與析構(gòu) template<class T, class Alloc> class simple_alloc { public:// 申請n個T類型對象大小的空間static T *allocate(size_t n){return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T));}// 申請一個T類型對象大小的空間static T *allocate(void){return (T*) Alloc::allocate(sizeof (T));}// 釋放n個T類型對象大小的空間static void deallocate(T *p, size_t n){if (0 != n)Alloc::deallocate(p, n * sizeof (T));}// 釋放一個T類型對象大小的空間static void deallocate(T *p){Alloc::deallocate(p, sizeof (T));} };3.5 對象的構(gòu)造與釋放
一切為了效率考慮,SGI-STL決定將空間申請釋放和對象的構(gòu)造析構(gòu)兩個過程分離開,因為有些對象的構(gòu)造不需要調(diào)用析構(gòu)函數(shù),銷毀時不需要調(diào)用析構(gòu)函數(shù),將該過程分離開可以提高程序的性能:
// 歸還空間時,先先調(diào)用該函數(shù)將對象中資源清理掉 template <class T> inline void destroy(T* pointer) {pointer->~T(); } // 空間申請好后調(diào)用該函數(shù):利用placement-new完成對象的構(gòu)造 template <class T1, class T2> inline void construct(T1* p, const T2& value) {new (p) T1(value); }注意:
1. 在釋放對象時,需要根據(jù)對象的類型確定是否調(diào)用析構(gòu)函數(shù)(類型萃取)
2. 對象的類型可以通過迭代器獲萃取到
4. 與容器結(jié)合
本例子給出list與空間配置器是如何結(jié)合的
template <class T, class Alloc = alloc> class list {// ...// 實例化空間配置器typedef simple_alloc<list_node, Alloc> list_node_allocator;// ... protected:link_type get_node(){ // 調(diào)用空間配置器接口先申請節(jié)點的空間return list_node_allocator::allocate();}// 將節(jié)點歸還給空間配置器void put_node(link_type p){list_node_allocator::deallocate(p);}// 創(chuàng)建節(jié)點:1. 申請空間 2. 完成節(jié)點構(gòu)造link_type create_node(const T& x){link_type p = get_node();construct(&p->data, x);return p;}// 銷毀節(jié)點: 1. 調(diào)用析構(gòu)函數(shù)清理節(jié)點中資源 2. 將節(jié)點空間歸還給空間配置器void destroy_node(link_type p){destroy(&p->data);put_node(p);}// ...iterator insert(iterator position, const T& x){link_type tmp = create_node(x);tmp->next = position.node;tmp->prev = position.node->prev;(link_type(position.node->prev))->next = tmp;position.node->prev = tmp;return tmp;}iterator erase(iterator position){link_type next_node = link_type(position.node->next);link_type prev_node = link_type(position.node->prev);prev_node->next = next_node;next_node->prev = prev_node;destroy_node(position.node);return iterator(next_node);}// ... };5.總結(jié)
- new運算分兩個階段:
(1)調(diào)用::operator new配置內(nèi)存;
(2)調(diào)用對象構(gòu)造函數(shù)構(gòu)造對象內(nèi)容
- delete運算分兩個階段:
(1)調(diào)用對象希構(gòu)函數(shù);
(2)調(diào)用operator delete釋放內(nèi)存
- 為了精密分工,STL allocator將兩個階段操作區(qū)分開來:
- 內(nèi)存配置有alloc::allocate()負責,內(nèi)存釋放由alloc::deallocate()負責;
- 對象構(gòu)造由::construct()負責,對象析構(gòu)由::destroy()負責。
同時為了提升內(nèi)存管理的效率,減少申請小內(nèi)存造成的內(nèi)存碎片問題。
- SGI STL采用了兩級配置器,當分配的空間大小超過128字節(jié)時,會使用第一級空間配置器;
- 當分配的空間大小小于128字節(jié)時,將使用第二級空間配置器。
- 第一級空間配置器直接使用malloc()、realloc()、free()函數(shù)進行內(nèi)存空間的分配和釋放
- 而第二級空間配置器采用了內(nèi)存池技術(shù),通過空閑鏈表來管理內(nèi)存。
總結(jié)
以上是生活随笔為你收集整理的STL中的空间配置器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STL中sort算法简析
- 下一篇: JQuery简介选择器