STL空间配置器

Posted 滴巴戈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL空间配置器相关的知识,希望对你有一定的参考价值。

1、什么是空间配置器?

  空间配置器负责空间配置与管理。配置器是一个实现了动态空间配置、空间管理、空间释放的class template。以内存池方式实现小块内存管理分配。关于内存池概念可以点击:内存池。

2、STL空间配置器产生的缘由

  在软件开发,程序设计中,我们不免因为程序需求,使用很多的小块内存(基本类型以及小内存的自定义类型)。在程序中动态申请,释放。这个过程过程并不是一定能够控制好的,于是乎出现以下问题:

问题1:就出现了内存碎片问题。(ps外碎片问题)

问题2:一直在因为小块内存而进行内存申请,调用malloc,系统调用产生性能问题。

注:内碎片:因为内存对齐/访问效率(CPU取址次数)而产生 如 用户需要3字节,实际得到4或者8字节的问题,其中的碎片是浪费掉的。

  外碎片:系统中内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费。如图:

(1)一级空间配置器和二级空间配置器实现思路:

空间配置策略:

 用户申请空间大于128?

  yes:调用一级空间配置器

  no:调用二级空间配置器

 客户端可以通过宏__USE_MALLOC进行自定义选择是否使用二级空间配置器。

  STL设计了双层级配置器,第一级配置直接使用malloc()和free();第二级配置器则视情况采用不同的策略,当配置区大于128bytes时,直接调用第一级配置器;当配置区块小于128bytes时,遍不借助第一级配置器,而使用一个memory pool来实现。究竟是使用第一级配置器还是第二级配置器,由一个__USE_MALLOC宏定义来控制。SGI STL中默认使用第二级配置器。

3、一级空间配置器

主要是Allocate()函数和Dellocate()函数,直接封装了malloc,free进行处理,增加了C++中的set_handler机制,增加内存分配时客户端可选处理机制。

 1 #define __TRACE_DEBUG(...)  \\
 2     __trace_debug(__FUNCTION__ , __FILE__, __LINE__, __VA_ARGS__);
 3 
 4 typedef void (*HANDLE_FUNC)();
 5 
 6 template<int inst>
 7 class MallocAllocTemplate
 8 {
 9 public:
10     //static void(*__malloc_alloc_oom_handler)();
11     static HANDLE_FUNC _malloc_alloc_oom_handler;
12     
13     void* OOM_Malloc(size_t n)
14     {
15         while (1)//死循环一直申请空间,直到申请成功,或失败抛异常
16         {
17             if (_malloc_alloc_oom_handler == 0)
18             {
19                 throw bad_alloc();
20             }
21             _malloc_alloc_oom_handler();//释放内存
22             void* second = malloc(n); //再次申请空间
23             if (second)
24                 return second;
25         }
26     }
27     // 1: 分配内存成功, 则直接返回
28     // 2: 若分配失败, 则检查是否设置处理的handler,
29     //有则调用以后再分配。 不断重复这个过程, 直到分配成功为止。
30     //没有设置处理的handler, 则直接结束程序。
31     static void* Allocate(size_t n)
32     {
33         __TRACE_DEBUG("一级空间配置器申请%ubytes\\n", n);
34 
35         void* first = malloc(n);
36         if (first == NULL)  //第一次申请空间失败
37         {
38             first = OOM_Malloc(0);
39         }
40         return first;
41     }
42     //realloc实现机制与allocate类似
43     void* OOM_Realloc(size_t n)
44     {
45         while (1)//死循环一直申请空间,直到申请成功,或失败抛异常
46         {
47             if (_malloc_alloc_oom_handler == 0)
48             {
49                 throw bad_alloc();
50             }
51             _malloc_alloc_oom_handler();
52             void* second = realloc(n); 
53             if (second)
54                 return second;
55         }
56     }
57     static void* Rllocate(size_t n)
58     {
59         __TRACE_DEBUG("一级空间配置器申请%ubytes\\n", n);
60 
61         void* first = realloc(n);
62         if (first == NULL) 
63         {
64             first = OOM_Realloc(0);
65         }
66         return first;
67     }
68 
69     static void Delloctate(void* p, size_t n)
70     {
71         __TRACE_DEBUG("一级空间配置器释放%ubytes\\n", n);
72         free(p);
73     }
74 
75     static HANDLE_FUNC SetMallocHandler(HANDLE_FUNC f)
76     {
77         HANDLE_FUNC old = f;
78         __malloc_alloc_oom_handler = f;
79         return old;
80     }
81 //     static void(*SetMallocHandler(void(*f)()))()
82 //     {
83 //         void(*old)() = __malloc_alloc_oom_handler;
84 //         __malloc_alloc_oom_handler = f;
85 //         return(old);
86 //     }
87 private:
88 };
89 
90 //分配内存失败处理函数的句柄函数指针
91 template<int inst>
92 HANDLE_FUNC __MallocAllocTemplate<inst>::__malloc_alloc_oom_handler = NULL;

SetMallocHandler(HANDLE_FUNC f)  

  malloc,free,realloc等库函数是向系统申请内存并且操作的函数。平时我们并不太会遇到内存空间分配不出来的情况,但是如果这一套程序是运行在服务器上的,各种各样的进程都需要内存。这样频繁的分配内存,终有一个时候,服务器再也分配不出内存,那么空间配置器该怎么办呢?这个函数指针指向的句柄函数就是处理这种情况的设计。

       SetMallocAllocHander()一般是自己设计的一种策略。这种策略想要帮助操作系统得到内存空间用以分配。所以,设计这个函数就是一个提升空间配置器效率的一个方法。如果并不想设计这个策略,也可以把句柄函数初始化为0。

__TRACE_DEBUG使用

  对于内存池的内部实现过程共还是比较复杂的,虽然代码量,函数比较简单。但是调用过程可能比较复杂。这时,如果我们选择debug调试,过程会相当的繁琐,需要仔细记录调用堆栈过程以及数据流向,逻辑变更等。对于楼主这种水货来说,估计完事就要苦了。

  所以,就__TRACE_DEBUG使用进行跟踪,打印数据流向,逻辑走向,文件,函数,方法,行位置。那么我们就能根据这个记录进行程序的排错以及调优了。

 1 //通过__TRACE_DEBUG做白盒测试
 2 
 3 //#define __DEBUG__
 4 static string GetFileName(const string& path)
 5 {
 6     char ch = \'/\';
 7 
 8 #ifdef _WIN32
 9     ch = \'\\\\\';
10 #endif
11 
12     size_t pos = path.rfind(ch);
13     if (pos == string::npos)
14         return path;
15     else
16         return path.substr(pos + 1);
17 }
18 
19 // 用于调试追溯的trace log
20 inline static void __trace_debug(const char* function,
21     const char * filename, int line, char* format, ...)
22 {
23     // 读取配置文件
24 #ifdef __DEBUG__
25     // 输出调用函数的信息
26     fprintf(stdout, "【 %s:%d】%s", GetFileName(filename).c_str(), line, function);
27 
28     // 输出用户打的trace信息
29     va_list args;
30     va_start(args, format);
31     vfprintf(stdout, format, args);
32     va_end(args);
33 #endif
34 }
35 
36 #define __TRACE_DEBUG(...)  __trace_debug(__FUNCTION__ , __FILE__, __LINE__, __VA_ARGS__);

4、二级空间配置器

二级空间配置器的实现就比较复杂了,主要由内存池以及伙伴系统:自由链表组成。

  1 template<bool threads, int inst>
  2 class DefaultAllocTemplate
  3 {
  4     enum { __ALIGN = 8 };  //(排列基准值,即排列间隔)
  5     enum { __MAX_BYTES = 128 };  //最大值
  6     enum { __NFREELISTS = __MAX_BYTES / __ALIGN };  //排列链
  7 public:
  8     static size_t FreeList_index(size_t n) //计算应该去取内存块的相应位置,对齐
  9     {
 10         return (n + __ALIGN - 1) / __ALIGN - 1;
 11     }
 12     static size_t Round_up(size_t bytes) //对齐
 13     {
 14         return ((bytes + __ALIGN - 1) & ~(__ALIGN - 1));
 15     }
 16 
 17     // 到内存池申请nobjs个对象
 18     static char* ChunkAlloc(size_t bytes, size_t& nobjs)
 19     {
 20         size_t needBytes = bytes * nobjs;
 21         size_t leftBytes = _endfree - _startfree;
 22         if (needBytes <= leftBytes)
 23         {
 24             __TRACE_DEBUG("狭义内存池有足够%u个对象\\n", nobjs);
 25 
 26             char* ret = _startfree;
 27             _startfree += needBytes;
 28             return ret;  //申请到20个
 29         }
 30         else if(leftBytes > bytes){
 31             nobjs = leftBytes / needBytes;
 32             __TRACE_DEBUG("狭义内存池只有%u个对象\\n", nobjs);
 33 
 34             char* ret = _startfree;
 35             _startfree += nobjs;
 36             return ret;  //申请到1~19个
 37         }
 38         else
 39         {
 40             //处理余下的小块内存
 41             if (leftBytes > 0)//挂到自由链表上的对应位置(头插)
 42             {
 43                 size_t index = FreeList_index(leftBytes);
 44                 ((obj*)_startfree)->_freelistlink = _freeList[index];
 45                 _freeList[index] = (obj*)_startfree;
 46             }
 47 
 48             //一个也没有
 49             size_t sizeToGet = needBytes * 2 + Round_up(_heapsize >> 4);
 50             __TRACE_DEBUG("一个对象都没有,到系统申请%ubytes\\n", sizeToGet);
 51 
 52             _startfree = (char*)malloc(sizeToGet);
 53             if (_startfree == NULL)
 54             {
 55                 // 系统已经没有足够的内存-尽力为之
 56                 // 到更大的自由链表中去取内存块,置入内存池
 57                 for (size_t i = FreeList_index(bytes); i < __NFREELISTS; i++)
 58                 {
 59                     if (_freeList[i]) //(头删)
 60                     {
 61                         _startfree = (char*)_freeList[i];
 62                         _freeList[i] = ((obj*)_startfree)->_freelistlink;
 63                         _endfree = _startfree + (i + 1) * __ALIGN;
 64                         return ChunkAlloc(bytes, nobjs);//重新申请
 65                     }
 66                 }
 67                 // 山穷水尽 -- 求助一级空间配置器
 68                 //自由链表中也没有分配到内存, 则再到一级配置器中分配内存,
 69                 //一级配置器中可能有设置的处理内存, 或许能分配到内存。
 70                 _endfree = NULL;
 71                 _startfree = (char*)MallocAllocTemplate<0>::Allocate(sizeToGet);
 72             }
 73             //更新内存池
 74             _heapsize += sizeToGet;
 75             _endfree = _startfree + sizeToGet;
 76             return ChunkAlloc(bytes, nobjs);  //重新申请
 77         } 
 78      }
 79     // 填充自由链表
 80     static void* Refill(size_t bytes)
 81     {
 82         size_t nobjs = 20; 
 83         __TRACE_DEBUG("到狭义内存池取%u个%ubytes字节的对象\\n", nobjs, bytes);
 84 
 85         char* chunk = ChunkAlloc(bytes, nobjs);
 86         __TRACE_DEBUG("取到了%u个对象,返回一个对象,将剩余的%u对象挂到自由链表的下面\\n", nobjs, nobjs - 1);
 87 
 88         if (nobjs == 1)//如果只分配到一块,直接使用它
 89         {
 90             return chunk;
 91         }
 92         size_t index = FreeList_index(bytes);
 93         obj *cur = NULL;
 94         obj *next = NULL;
 95         for (size_t i = 1; i < nobjs; i++)
 96         {
 97             next = (obj*)(chunk + i*bytes);
 98             if (_freeList[index] == NULL)
 99             {
100                 _freeList[index] = next;
101             }
102             else {
103                 cur->_freelistlink = next;
104             }
105             cur = next;
106         }
107         if (cur)
108         {
109             cur->_freelistlink = NULL;
110         }
111         return chunk;
112     }
113     static void* Allocate(size_t n)
114     {
115         __TRACE_DEBUG("二级空间配置器申请%ubytes\\n", n);
116 
117         if (n>__MAX_BYTES)
118         {
119             return MallocAllocTemplate<0>::Allocate(n);
120         }
121 
122         // ps:多线程环境需要考虑加锁
123         size_t index = FreeList_index(n);  //计算n在自由链表中的位置
124         if (_freeList[index] == NULL)  //自由链表为空
125         {
126             __TRACE_DEBUG("在freeList[%u]下面没有内存块对象\\n", index);
127 
128             return Refill(Round_up(0));  //获取大块内存插入到自由链表中
129         }
130         else{  //自由链表不为空,取第一块,类似于头删
131             __TRACE_DEBUG("在freeList[%u]取一个内存块对象\\n", index);
132 
133             obj* result = _freeList[index];
134             _freeList[index] = result->_freelistlink;
135             return result;
136         }
137     }
138     static void Dellocate(void* ptr, size_t n)
139     {
140 
141         if (n > __MAX_BYTES) 
142         {
143             MallocAllocTemplate<0>::Dellocate(ptr, n);
144             return;
145         }
146 
147         // ps:多线程环境需要考虑加锁
148         size_t index = FreeList_index(n);
149         __TRACE_DEBUG("将释放的内存块对象挂到freeList[%u]\\n", index);
150         
151         if (ptr)
152         {
153             obj* back = (obj*)ptr; //将释放的内存块对象挂到_freeList[index]上,类似于头插
154             back->_freelistlink = _freeList[index];
155             _freeList[index] = back;
156         }
158     }
159 protected:
160     //定义自由链表
161     union obj
162     {
163         obj* _freelistlink;  //指向下一个内存块的指针
164         char clientdata[1]; /* The client sees this.        */
165     };
166     static obj* _freeList[__NFREELISTS];//自由链表
167 
168     //狭义内存池
169     static char* _startfree;  //内存池水位线开始处
170     static char* _endfree;  //内存池水位线结束处
171     static size_t _heapsize;   //从系统堆分配的总大小
172 };

代码中逻辑明了,而且已经注释得很清楚了,这里就不在赘述了。

 1 template <bool threads, int inst>
 2 char* DefaultAllocTemplate<threads, inst>::_startfree = 0;
 3 
 4 template <bool threads, int inst>
 5 char* DefaultAllocTemplate<threads, inst>::_endfree = 0;
 6 
 7 template <bool threads, int inst>
 8 size_t DefaultAllocTemplate<threads, inst>::_heapsize = 0;
 9 
10 template <bool threads, int inst>
11 typename DefaultAllocTemplate<threads, inst>::obj* DefaultAllocTemplate<threads, inst>::_freeList[__NFREELISTS] = { 0 };
12 
13 #ifdef __USE_MALLOC
14 typedef MallocAllocTemplate<0> alloc;
15 #else
16 typedef DefaultAllocTemplate<false, 0> alloc;
17 #endif //__USE_MALLOC
18 
19 template<class T, class Alloc>
20 class SimpleAlloc
21 {
22 public:
23     static T* Allocate(size_t n)
24     {
25         return 0 == n ? 0 : (T*)Alloc::Allocate(n * sizeof(T));
26     }
27 
28     static T* Allocate(void)
29     {
30         return (T*)Alloc::Allocate(sizeof(T));
31     }
32 
33     static void Dellocate(T *p, size_t n)
34     {
35         if (0 != n)
36             Alloc::Dellocate(p, n * sizeof(T));
37     }
38 
39     static void Dellocate(T *p)
40     {
41         Alloc::Dellocate(p, sizeof(T));
42     }
43 };
44 
45 void TestAlloc1()
46 {
47     void *p1 = DefaultAllocTemplate<false, 0>::Allocate(200);
48      DefaultAllocTemplate<false, 0>::Dellocate(p1, 200);
49 
50      void *p2 = DefaultAllocTemplate<false, 0>::Allocate(25);
51      void *p3 = DefaultAllocTemplate<false, 0>::Allocate(25);
52  
53      DefaultAllocTemplate<false, 0>::Dellocate(p2, 25);
54      DefaultAllocTemplate<false, 0>::Dellocate(p3, 25);
55 }
56 
57 void TestAlloc2()
58 {
59     cout << " 测试系统堆内存耗尽 " << endl;
60 
61     DefaultAllocTemplate<false, 0&g

以上是关于STL空间配置器的主要内容,如果未能解决你的问题,请参考以下文章

STL空间配置器

STL空间配置器

STL源码剖析 — 空间配置器(allocator)

SGI STL 空间配置器

STL实现简单的空间配置器

STL实现简单的空间配置器