SGI STL 空間配置器(allocator)源碼剖析

空間配置器的做用

咱們知道,stl中的數據都是放到容器中的,容器須要存儲空間,空間配置器就是負責容器存儲空間的分配、回收等一系列內存操做。
 

STL標準規範中描述的allocator模版類的必要聲明:

 1 <strong>Constructors</strong>
 2 // Constructors used to create allocator objects.
 3 allocator(); // Default constructor 
 4 allocator(const allocator<Type>& _Right);  // Copy constructor
 5 template<class Other>                      // Template copy constructor
 6 allocator(const allocator<Other>& _Right);
 7 
 8 <strong>Typedefs</strong>
 9 const_pointer   // A type that provides a constant pointer to the type of object managed by the allocator. 
10 const_reference // A type that provides a constant reference to type of object managed by the allocator. 
11 difference_type // A signed integral type that can represent the difference between values of pointers to the type of object managed by the allocator. 
12 pointer         // A type that provides a pointer to the type of object managed by the allocator. 
13 reference       // A type that provides a reference to the type of object managed by the allocator.
14 size_type       // An unsigned integral type that can represent the length of any sequence that an object of template class allocator can allocate. 
15 value_type      // A type that is managed by the allocator.
16  
17 <strong>Member Functions</strong>
18 // Finds the address of an object whose value is specified.
19 pointer (reference _Val) const;
20 const_pointer (const_reference _Val) const;
21 
22 // Allocates a block of memory large enough to store at least some specified number of elements.
23 pointer allocate(size_type _Count, const void* _Hint);
24  
25 // Constructs a specific type of object at a specified address that is initialized with a specified value.
26 void construct(pointer _Ptr, const Type& _Val);
27  
28 // Frees a specified number of objects from storage beginning at a specified position.
29 void deallocate(pointer _Ptr, size_type _Count);
30  
31 // Calls an objects destructor without deallocating the memory where the object was stored.
32 void destroy(pointer _Ptr);
33  
34 // Returns the number of elements of type Type that could be allocated by an object of class allocator before the free memory is used up.
35 size_type max_size( ) const; 
36 
37 // A structure that enables an allocator for objects of one type to allocate storage for objects of another type.
38 template<class _Other>
39 struct rebind {
40    typedef allocator<_Other> other;
41 };
42 
43 <strong>Operators overload</strong>
44 template<class Other>
45 allocator<Type>& operator=(const allocator<Other>& _Right);  // Assignment operator
46 
47  
View Code

 

SGI STL使用的空間配置器

SGI STL底層沒有采用上述STL標準規範中的聲明,它的配置器不同凡響,其名稱是alloc而非allocator,並且不接受任何參數。
SGI STL中的每種容器都已經指定其缺省的空間配置器alloc,咱們在使用時不多須要自行指定配置器。
SGI STL空間配置器的設計哲學:
  • 從堆中申請內存空間
  • 考慮多線程狀態
  • 考慮內存不足時的應變措施
  • 考慮過多「小型區塊」可能形成的內存碎片問題
SGI STL空間配置器alloc的設計描述:
  • 使用雙層級配置器
  • 第一級配置器(__malloc_alloc_template)直接使用malloc() 和 free() 
  • 第二級配置器(__default_alloc_template)視狀況採用不一樣策略:當配置區塊超過128byte時,視之爲「足夠大」,便調用第一級配置器;不然,視之爲「太小」,爲了提升效率(1.減小內存碎片;2.下降額外負擔:直接使用malloc()申請內存時,系統會要多分配一些內存(內存頭)用來管理所分配的內存空間),採用複雜的內存池(memory pool)技術(見下文詳解),再也不求助於第一級配置器
  • SGI STL爲了使配置器的接口可以符合STL的標準規範,提供了一個包裝接口simple_alloc:
 1 template<class T, class Alloc>
 2 class simple_alloc {
 3 public:
 4     static T *allocate(size_t n)
 5                 { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
 6     static T *allocate(void)
 7                 { return (T*) Alloc::allocate(sizeof (T)); }
 8     static void deallocate(T *p, size_t n)
 9                 { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
10     static void deallocate(T *p)
11                 { Alloc::deallocate(p, sizeof (T)); }
12 };
View Code

 


第一級配置器__malloc_alloc_template源碼剖析

 1 <span style="font-size:10px;">// 基於malloc()的配置器.一般比第二級配置器(__default_alloc_template)慢.
 2 // 一般是線程安全的,而且對存儲空間的使用更加高效.
 3 #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG
 4 # ifdef __DECLARE_GLOBALS_HERE
 5 void (* __malloc_alloc_oom_handler)() = 0;     // 內存不足處理函數指針
 6 // g++ 2.7.2 does not handle static template data members.
 7 # else
 8 extern void (* __malloc_alloc_oom_handler)();
 9 # endif
10 #endif
11 
12 template <int inst>  // 沒有模版型別參數,至於非型別參數inst沒有用到
13 class __malloc_alloc_template {
14 private:   // 用來處理內存不足的狀況
15     static void *oom_malloc(size_t);
16     static void *oom_realloc(void *, size_t);
17 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
18     static void (* __malloc_alloc_oom_handler)();
19 #endif
20 
21 public:
22 
23     static void * allocate(size_t n)
24     {
25         void *result = malloc(n);    // 直接調用malloc()
26         if (0 == result) 
27             result = oom_malloc(n);  // 內存申請失敗,調用內存不足處理函數 
28         return result;
29     }
30 
31     static void deallocate(void *p, size_t /* n */)
32     {
33         free(p);  // 直接調用free()
34     }
35 
36     static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)
37     {
38         void * result = realloc(p, new_sz);  // 直接使用realloc()
39         if (0 == result) 
40             result = oom_realloc(p, new_sz); // 內存申請失敗,調用內存不足處理函數 
41         return result;
42     }
43 
44     // 指定內存不足處理函數句柄
45     static void (* set_malloc_handler(void (*f)()))()
46     {
47         void (* old)() = __malloc_alloc_oom_handler;
48         __malloc_alloc_oom_handler = f;
49         return(old);
50     }
51 
52 };
53 
54 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
55 template <int inst>
56 void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0; // 初值爲0,需client設定
57 #endif
58 
59 // 內存不足處理函數:malloc()申請內存失敗
60 template <int inst>
61 void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
62 {
63     void (* my_malloc_handler)();
64     void *result;
65 
66     for (;;) {  // 不斷嘗試
67         my_malloc_handler = __malloc_alloc_oom_handler;
68         if (0 == my_malloc_handler) { 
69             __THROW_BAD_ALLOC; } // client未設定處理函數,直接拋出異常  
70         (*my_malloc_handler)();  // 調用內存不足處理函數
71         result = malloc(n);      // 再次嘗試申請內存
72         if (result) 
73             return(result);      // 申請成功
74     }
75 }
76 
77 // 內存不足處理函數:realloc()申請內存失敗(與oom_malloc()相似)
78 template <int inst>
79 void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
80 {
81     void (* my_malloc_handler)();
82     void *result;
83 
84     for (;;) {
85         my_malloc_handler = __malloc_alloc_oom_handler;
86         if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
87         (*my_malloc_handler)();
88         result = realloc(p, n);
89         if (result) return(result);
90     }
91 }
92 
93 typedef __malloc_alloc_template<0> malloc_alloc; // inst直接被指定爲0</span>
View Code

 

總結:

  • 第一層配置器比較簡單,直接調用相應的C函數
  • 並實現出相似C++ new-handler機制,來處理內存不足的狀況

 

第二級配置器__default_alloc_template源碼剖析

  1 // 多線程搞不懂,故去掉了線程相關代碼,留待之後分析
  2 template <bool threads, int inst>
  3 class __default_alloc_template {
  4 
  5 private:
  6     // Really we should use static const int x = N
  7     // instead of enum { x = N }, but few compilers accept the former.
  8     // 唉,爲了兼容性,考慮得太周到了
  9 # ifndef __SUNPRO_CC
 10     enum {__ALIGN = 8};
 11     enum {__MAX_BYTES = 128};
 12     enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
 13 # endif
 14     // 內存對齊:將bytes上調至8的倍數
 15     static size_t ROUND_UP(size_t bytes) {
 16         return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
 17     }
 18 __PRIVATE:
 19     // free_list的節點構造
 20     union obj {
 21         union obj * free_list_link;
 22         char client_data[1];    /* The client sees this. 這裏沒搞懂        */  
 23     };
 24 private:
 25 # ifdef __SUNPRO_CC
 26     static obj * __VOLATILE free_list[]; 
 27     // Specifying a size results in duplicate def for 4.1
 28 # else
 29     static obj * __VOLATILE free_list[__NFREELISTS]; // free_list數組,16個元素
 30 # endif
 31     // 根據bytes大小獲取free_list的數組下標,從0開始
 32     static  size_t FREELIST_INDEX(size_t bytes) {
 33         return (((bytes) + __ALIGN-1)/__ALIGN - 1);
 34     }
 35 
 36     // Returns an object of size n, and optionally adds to size n free list.
 37     // 返回一個大小爲n的對象,並可能加入大小爲n的其它區塊到free list中
 38     static void *refill(size_t n);
 39 
 40     // Allocates a chunk for nobjs of size "size".  nobjs may be reduced
 41     // if it is inconvenient to allocate the requested number.
 42     // 配置一塊空間,可容納nobjs個大小爲"size"的區塊
 43     // 若是不能配置nobjs個區塊,nobjs的大小可能會減小(按引用傳遞的)
 44     static char *chunk_alloc(size_t size, int &nobjs);
 45 
 46     // 內存池狀態
 47     static char *start_free;  // 內存池起始位置。只在chunk_alloc()中變化
 48     static char *end_free;    // 內存池結束位置。只在chunk_alloc()中變化
 49     static size_t heap_size;  // 從堆中申請的內存大小
 50 
 51 public:
 52 
 53     /* n must be > 0      */
 54     static void * allocate(size_t n)
 55     {
 56         obj * __VOLATILE * my_free_list;
 57         obj * __RESTRICT result;
 58 
 59         if (n > (size_t) __MAX_BYTES) {  
 60             return(malloc_alloc::allocate(n)); // n大於128byte,直接調用第一級配置器
 61         }
 62         my_free_list = free_list + FREELIST_INDEX(n); // 從16個free_list中獲取合適的一個
 63         result = *my_free_list;
 64         if (result == 0) {
 65             void *r = refill(ROUND_UP(n)); // 沒有可用的free_list,從新填充free_list
 66             return r;
 67         }
 68         // 調整free_list:將已使用的內存區塊從free_list中移除
 69         *my_free_list = result -> free_list_link; 
 70         return (result);
 71     };
 72 
 73     /* p may not be 0 */
 74     static void deallocate(void *p, size_t n)
 75     {
 76         obj *q = (obj *)p;
 77         obj * __VOLATILE * my_free_list;
 78 
 79         if (n > (size_t) __MAX_BYTES) {
 80             malloc_alloc::deallocate(p, n); // n大於128byte,直接調用第一級配置器
 81             return;
 82         }
 83         my_free_list = free_list + FREELIST_INDEX(n); // 從16個free_list中獲取合適的一個
 84         // 調整free_list: 將回收內存區塊從新添加到free_list中
 85         q -> free_list_link = *my_free_list;
 86         *my_free_list = q;
 87     }
 88 
 89     static void * reallocate(void *p, size_t old_sz, size_t new_sz);
 90 
 91 } ;
 92 
 93 // static數據的定義與初值設定
 94 template <bool threads, int inst>
 95 char *__default_alloc_template<threads, inst>::start_free = 0;
 96 
 97 template <bool threads, int inst>
 98 char *__default_alloc_template<threads, inst>::end_free = 0;
 99 
100 template <bool threads, int inst>
101 size_t __default_alloc_template<threads, inst>::heap_size = 0;
102 
103 template <bool threads, int inst>
104 __default_alloc_template<threads, inst>::obj * __VOLATILE
105     __default_alloc_template<threads, inst> ::free_list[
106 # ifdef __SUNPRO_CC
107         __NFREELISTS
108 # else
109         __default_alloc_template<threads, inst>::__NFREELISTS
110 # endif
111     ] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
112 // The 16 zeros are necessary to make version 4.1 of the SunPro
113 // compiler happy.  Otherwise it appears to allocate too little
114 // space for the array.
115 
116 
117 
118 /* We allocate memory in large chunks in order to avoid fragmenting     */
119 /* the malloc heap too much.                                            */
120 /* We assume that size is properly aligned.                             */
121 /* We hold the allocation lock.                                         */
122 // 爲了不堆中有過多的內存碎片,咱們每次申請一大塊內存空間
123 // 咱們假定這個空間大小是內存對齊的
124 template <bool threads, int inst>
125 char*
126     __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
127 {
128     char * result;
129     size_t total_bytes = size * nobjs;
130     size_t bytes_left = end_free - start_free;  // 內存池剩餘空間大小
131 
132     if (bytes_left >= total_bytes) {   // 內存池剩餘空間徹底知足需求量
133         result = start_free;
134         start_free += total_bytes;
135         return(result);
136     } else if (bytes_left >= size) {   // 內存池剩餘空間知足至少一個區塊
137         nobjs = bytes_left/size;       // 調整nobjs的值
138         total_bytes = size * nobjs;
139         result = start_free;
140         start_free += total_bytes;
141         return(result);
142     } else {                           // 內存池剩餘空間連一個區塊都知足不了
143         // 計算需從堆中申請的內存塊大小
144         size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
145 
146         // 先把內存池剩餘空間分配給合適的free_list
147         if (bytes_left > 0) {  
148             obj * __VOLATILE * my_free_list =
149                 free_list + FREELIST_INDEX(bytes_left);
150 
151             ((obj *)start_free) -> free_list_link = *my_free_list;
152             *my_free_list = (obj *)start_free;
153         }
154 
155         start_free = (char *)malloc(bytes_to_get); // 從堆中申請內存,補充內存池
156         if (0 == start_free) {                     // 申請內存失敗
157             int i;
158             obj * __VOLATILE * my_free_list, *p;
159             // Try to make do with what we have.  That can't
160             // hurt.  We do not try smaller requests, since that tends
161             // to result in disaster on multi-process machines.
162             // 先嚐試檢查咱們手上(free_list)擁有的東西。這不會形成傷害。
163             // 咱們不打算檢查比size更小的區塊,由於那在多進程機器上容易致使災難。
164             for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
165                 my_free_list = free_list + FREELIST_INDEX(i);
166                 p = *my_free_list;
167                 if (0 != p) {   // 存在未用區塊
168                     // 釋放該未用區塊
169                     *my_free_list = p -> free_list_link; 
170                     start_free = (char *)p;
171                     end_free = start_free + i;
172                     return(chunk_alloc(size, nobjs)); // 遞歸調用本身,從新分配
173                     // Any leftover piece will eventually make it to the
174                     // right free list.
175                     // 任何內存池剩餘空間終將被編入適當的free list
176                 }
177             }
178 
179             // 55,山窮水盡了
180             end_free = 0;    
181             // 調用第一級配置器,看看內存不足處理函數能不能起做用
182             start_free = (char *)malloc_alloc::allocate(bytes_to_get);
183             // This should either throw an
184             // exception or remedy the situation.  Thus we assume it
185             // succeeded.
186             // 要麼拋出異常,要麼內存不足的狀況得以改善,咱們假定它成功了
187         }
188         heap_size += bytes_to_get;
189         end_free = start_free + bytes_to_get;
190         return(chunk_alloc(size, nobjs));  // 遞歸調用本身,從新分配
191     }
192 }
193 
194 
195 /* Returns an object of size n, and optionally adds to size n free list.*/
196 /* We assume that n is properly aligned.                                */
197 /* We hold the allocation lock.                                         */
198 // 返回一個大小爲n的指針對象,而且有可能會爲適當的free list增長節點
199 // 假定n已經適當調整到8的倍數了
200 template <bool threads, int inst>
201 void* __default_alloc_template<threads, inst>::refill(size_t n)
202 {
203     int nobjs = 20;  // 缺省取得20個新節點
204     char * chunk = chunk_alloc(n, nobjs);
205     obj * __VOLATILE * my_free_list;
206     obj * result;
207     obj * current_obj, * next_obj;
208     int i;
209 
210     if (1 == nobjs) 
211         return(chunk);  // 只得到一個節點,直接返回
212     // 不然準備調整free list,歸入新節點
213     my_free_list = free_list + FREELIST_INDEX(n); // 找到合適的free list
214 
215     /* Build free list in chunk */
216     // 歸入新節點
217     result = (obj *)chunk;
218     *my_free_list = next_obj = (obj *)(chunk + n); 
219     for (i = 1; ; i++) {
220         current_obj = next_obj;
221         next_obj = (obj *)((char *)next_obj + n);
222         if (nobjs - 1 == i) {
223             current_obj -> free_list_link = 0;
224             break;
225         } else {
226             current_obj -> free_list_link = next_obj;
227         }
228     }
229     return(result);
230 }
231 
232 template <bool threads, int inst>
233 void*
234     __default_alloc_template<threads, inst>::reallocate(void *p,
235     size_t old_sz,
236     size_t new_sz)
237 {
238     void * result;
239     size_t copy_sz;
240 
241     if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {
242         return(realloc(p, new_sz));  // 疑問:這裏怎麼不直接調用第一級配置器裏面的realloc
243     }
244     if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);
245     result = allocate(new_sz);
246     copy_sz = new_sz > old_sz? old_sz : new_sz;
247     memcpy(result, p, copy_sz);
248     deallocate(p, old_sz);
249     return(result);
250 }
251 
252 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
253 typedef __default_alloc_template<false, 0> single_client_alloc;
View Code

 

總結:
  • 第二層配置器的核心就是內存池
  • SGI STL的內存池經過free_list數組來管理,數組中包含16個free list,每一個free list節點大小依次從八、1六、24遞增到128byte
  • 分配內存時(假定分配大小在128byte內),首先根據分配大小找到合適的free list,若是該free list中沒有可用的節點,則調用refill()函數從內存池中取空間給該free list用,而後返回第一個節點的內存空間,並調整該free list
  • refill()缺省取20個新節點(區塊),經過調用chunk_alloc(),而後根據取得新節點的實際個數,調整相應的free list(大於1)
  • chunk_alloc()內部實現最爲複雜,根據內存池剩餘空間大小與需求量的關係,分爲三種狀況,具體看代碼,再也不贅述

 
1
相關文章
相關標籤/搜索