有关SGI STL的alloc

打印 上一主题 下一主题

主题 536|帖子 536|积分 1608

    在STL的利用者层面上,空间设置器一般是隐藏的,利用者不必要知道其具体实现细节即
可进利用用;但是从STL的实现角度来说,由于整个STL的操作对象都存放在容器之内,而容器
必要设置肯定的空间来进行存放数据,因此,想要实现或者说深入了解STL的第一步便是掌握
空间设置器的原理。


目次
紧张还是说说SGI版本的STL的设置器


我们知道,stl有容器,空间设置器,适配器,迭代器,仿函数以及算法这6个组件:他们之间的运行关系大概是:容器通过设置器去获得数据和存储空间,算法通过迭代器获取容器内容,仿函数可以协助算法完成不同的计谋变化,配接器可以修饰或套界仿函数。
allocator是STL的紧张构成,但是一般用户不怎么熟悉他,由于allocator隐藏在所有容器(包括vector)身后,默默完成内存设置与开释,对象构造和析构的工作


首先我们知道c++中内存分配和开释操作是由new和delete去完成的
  1. class A {};
  2. A* p = new A;
  3. //...执行其他操作
  4. delete p;
  5. 在我们使用new来建造一个对象的时候,一般是分为两个步骤的:
  6.         1.调用::operator new()来构建空间;
  7.         2.调用对象的构造函数。
  8.         注:::operator new内部由malloc实现、省略指针转换这步。
  9.         同理,当我们使用delete函数的时候:
  10.         1.调用对象的析构函数
  11.         2.调用::operator delete()释放空间。
  12.         注:::operator delete内部由free实现
复制代码
在STL的实现中,为了精细分工、STL allocator决定将上述步骤的两阶段操作分开来,内存
设置操作由alloc::allocate()负责,内存开释由alloc::deallocate负责,对象构建
操作由construct负责,对象析构操作由destroy负责。对于实现我就不过多赘述了
下面紧张还是说说SGI版本的STL的设置器

对于SGI STL的实现来说、他这里含有了两个不同的空间设置器
第一个是一个符合部门尺度、名为allocatr的设置器std::allocator
  1. 仅仅是将::operator new()和
  2. ::operator delete()做了一层简单的封装,因此效率比较差。
复制代码
它存在的意义仅在于为用户提供一个兼容老代码的折衷方法,我们不建议利用,而且SGI内部也倒霉用这种尺度的设置器,看一下了解一下
  1. #ifnedf DEFALLOC_H
  2. #define DEFALLOC_H
  3. #include <new.h>
  4. #include <stddef.h>
  5. #include <stdlib.h>
  6. #include <limits.h>
  7. #include <iostream.h>
  8. #include <algobase.h>
  9. template <class T>
  10. inline T* allocate(ptrdiff_t size, T*) {
  11.     set_new_handler(0);
  12.     T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
  13.     if (tmp == 0) {
  14.         cerr << "out of memory" << endl;
  15.         exit(1);
  16.     }
  17.     return tmp;
  18. }
  19. template <class T>
  20. inline void deallocate(T* buffer) {
  21.     ::operator delete(buffer);
  22. }
  23. template <class T>
  24. class allocator {
  25. public:
  26.     typedef T value_type;
  27.     typedef T* pointer;
  28.     typedef const T* const_pointer;
  29.     typedef T& reference;
  30.     typedef const T& const_reference;
  31.     typedef size_t size_type;
  32.     typedef ptrdiff_t difference_type;
  33.     pointer allocate(size_type n) { return ::allocate((difference_type)n, (pointer)0); }
  34.     void deallocate(pointer p) { ::deallocator(p); }
  35.     pointer address(reference x) { return (pointer)&x; }
  36.     const_pointer const_address(const_reference x) { return (const_pointer)&x; }
  37.     size_type init_page_size() {
  38.         return max(size_type(1), size_type(UINT_MAX/sizeof(T)));
  39.     }
  40. };
  41. class allocator<void> {
  42. public:
  43.     typedef void* pointer;
  44. };
复制代码
第二个是我们必要重点掌握的特殊设置器 std::alloc

这个设置器是SGI STL的默认设置器,它在<memory>中实现。
<memory>中包含三个文件:


  • <stl_construct.h>:定义了全局函数construct()和destroy(),负责对象构造和析构。
  • <stl_alloc.h>:内存设置和开释在此处实现,其内部有两级设置器第一级结构简单,封装malloc()和free(),第二级实现了自由链表和内存池,用于提升大量小额内存设置时的性能。
  • <stl_uninitialiezed.h>:一些用于用于填充和拷贝大块内存的全局函数。
对象构造和/析构,与内存设置/开释是分离实现的。
  1. SGI对于alloc函数的要求如下所示:
  2.         1.向system heap要求空间
  3.         2.考虑多线程状态
  4.         3.考虑内存不足的应变措施
  5.         4.考虑过多“小型区块”可能造成的内存碎片问题。
复制代码
 

 两层设置器的关系如下:
根据情况来判定,如果设置区块大于128bytes,说明“足够大”,调用第一级设置器,而小于等于128bytes,则采用复杂内存池(memory pool)来管理。

同时为了自由选择,STL又规定了 __USE_MALLOC 宏,如果它存在则直接调用第一级设置器,不但是直接调用第二级设置器。SGI未定义该宏,也就是说默认利用第二级设置器
 由于一级设置器存在内存碎片的问题,所以有了二级设置器

转自《STL源码剖析》 的SGI STL特殊设置器的内部实现图

图示左边是用户代码,右边是STL的内部。
我们可以看到,用户代码实例化一个vector对象,vector对象调用alloc的接口,注意,不同于之前尺度的allocator,这里不必要实例化一个空间设置器,只必要调用alloc的静态函数就行了。
std::alloc的接口与尺度非常相似:


  • static T* allocate()函数负责空间设置,返回一个T对象巨细的空间。
  • static T* allocate(size_t)函数负责批量空间设置。
  • static void deallocate(T*)函数负责空间开释。
  • static void deallocate(T*,size_t)函数负责批量空间开释。
在接口之下,我们看到另有两级设置器,上面的接口根据情况调用这两个设置器,第二级设置器实现了内存池和自由链表,当步调多次进行小空间的设置时,可以从内存池和自由链表中获取空间,减少系统调用,提升性能。当进行大空间的设置时,上面的接口直接调用第一级设置器。终极,它们都是用malloc()和free()来设置和开释空间。
一般C++开发者了解到此已经足够应付大多数开发场景了。
这样看起来std::alloc的结构还算清晰,但是实际实现还会出现多种边界情况,比如,当系统调用malloc()空间不足时,我们必要让第一级设置器来处理,这大概涉及从第二级设置器中回收已经缓存但还未利用的空间,事情就变得繁琐了。
下面来手撕两层设置器的实现:


  • 一级空间设置器
一级设置器直接用C中的malloc()以及free()函数来进行处理,当我们的内存分配不足的时候,我们调用oom_malloc()函数和oom_realloc进行处理,这两个函数里面都有循环,不断地去调用“内存不足,要调用例程”,行止理。
  1. #if 1
  2. #include<new>
  3. #define __THROW_BAD_ALLOC throw bad_alloc
  4. #else !defined(__THROW_BAD_ALLOC)
  5. #define __THROW_BAD_ALLOC cerr<<"out of memory"<<endl;
  6. #endif
  7. template<int inst>
  8. class __malloc_alloc_template
  9. {
  10. private:
  11.         static void* oom_malloc(size_t);//malloc--指针函数
  12.         static void* oom_realloc(void*, size_t);//realloc--指针函数
  13.         static void(*__malloc_alloc_oom_handler)();//函数指针
  14. public:
  15.         static void* allocate(size_t n)//申请空间
  16.         {
  17.                 void* result = malloc(n);//第一级配置器直接使用malloc
  18.                 //如果无法满足需求,费用oom_malloc()
  19.                 if (result == 0) {
  20.                         result == oom_malloc(n);
  21.                 }
  22.                 return result;
  23.         }
  24.         static void deallocate(void* p, size_t)
  25.         {
  26.                 free(p);//第一级配置器直接使用free()
  27.         }
  28.         static void* reallocate(void* p, size_t)//不够了追加,是对allocate的补救
  29.         {
  30.                 void* result = realloc(p, new_sz);//第一级配置器直接使用realloc
  31.                 //无法满足需求,改用oom_realloc()
  32.                 if (result == 0) {
  33.                         result = oom_realloc(p, new_sz);
  34.                         return result;
  35.                 }
  36.         }
  37.         //下面这个可以通过他去指定自己的
  38.         //out-of-memory handler
  39.         static void (*set_malloc_handler(void(*f)()))()
  40.         {
  41.                 void (*old)() = __malloc_alloc_oom_handler;
  42.                 __malloc_alloc_oom_handler = f;
  43.                 return old;
  44.         }
  45. };
  46. //初始值为0,可以在客户端自己设定
  47. template<int inst>
  48. void(*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
  49. template<int inst>
  50. void* __malloc_alloc_template<inst>::oom_malloc(size_t n)
  51. {
  52.         void (*my_malloc_handler)();
  53.         void* result;
  54.         for (;;)//死循环,不断尝试释放,配置,释放,配置。。
  55.         {       
  56.                 my_malloc_handler = __malloc_alloc_oom_handler;
  57.                 if (0 == my_malloc_handler)
  58.                 {
  59.                         __THROW_BAD_ALLOC;
  60.                 }
  61.                 (*my_malloc_handler)();//调用处理例程,企图释放内存
  62.                 result = malloc(n);//尝试配置内存
  63.                 if (result)
  64.                         return result;
  65.         }
  66. }
  67. template<int inst>
  68. void* __malloc_alloc_template<inst>::oom_realloc(void* p, size_t n)
  69. {
  70.         void (*my_malloc_handler)();
  71.         void* result;
  72.         for (;;)
  73.         { //不断尝试释放、配置、再释放、再配置。。
  74.                 my_malloc_handler = __malloc_alloc_oom_handler;
  75.                 if (0 == my_malloc_handler)
  76.                 {
  77.                         __THROW_BAD_ALLOC;
  78.                 }
  79.                 (*my_malloc_handler)();//调用处理例程、企图释放释放内存
  80.                 result = realloc(p, n);//再次尝试配置内存
  81.                 if (result)
  82.                         return result;
  83.         }
  84. }
  85. typedef __malloc_alloc_template<0> malloc_alloc;
  86. void Out_Of_Memory()//测试
  87. {
  88.         cout << "this is out of memory" << endl;
  89.         // exit(1);
  90. }
  91. //测试
  92. int main()
  93. {
  94.         __malloc_alloc_template<0>::set_malloc_handler(Out_Of_Memory);
  95.         int* p = (int*)__malloc_alloc_template<0>::allocate(sizeof(int) * 536870911);
  96.         return 0;
  97. }
复制代码


  • 二级空间设置器
       第二级空间设置器多了一些机制,避免太多小额区块造成的内存的碎片,小额区块带来的其实
不仅是内存碎片,设置时的额外负担也是一个大问题(cookie),SGI第二级设置器的做法是,
如果区块够大,超过4096bytes时,就移交第一级设置器,否则就以内存池的方式管理:每次
设置一大块内存,并维护对应的自由链表,下次若再有雷同巨细的内存需求,就直接从free list
中拔出。如果哀求端开释小额内存,就由设置器回收到free list中,为了方便管理,SGI第二级
设置器会主动将任何小额区块的内存需求量上调至8的倍数。

 
   

  1. typedef __malloc_alloc_template<0> malloc_alloc;
  2. enum { __ALIGN = 8 };//小区块的上调边界
  3. enum { __MAX_BYTES = 128 };//小型区块的上线
  4. enum { __NFREELISTS = __MAX_BYTES / __ALIGN };//自由链表个数
  5. template<bool threads, int inst>
  6. class __default_alloc_template
  7. {
  8. private:
  9.         //把ROUND_UP上调到8的倍数
  10.         static size_t ROUND_UP(size_t bytes)
  11.         {
  12.                 return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
  13.         }
  14. private:
  15.         union obj//共用体
  16.         {
  17.                 union obj* free_list_link;
  18.                 char client_data[1];
  19.         };
  20. private:
  21.         //16个自由链表
  22.         static obj* free_list[__NFREELISTS];
  23.         //下面根据区块的大小,决定使用第n号free_list,n从1开始算
  24.         static size_t FREELIST_INDEX(size_t bytes)
  25.         {
  26.                 return ((bytes)+__ALIGN - 1) / __ALIGN - 1;
  27.         }
  28.         //返回一个大小为n的对象,并可能加入大小为n的其他区块到 free list
  29.         static void* refill(size_t n)
  30.         {
  31.                 int nobjs = 20;
  32.                 char* chunk = chunk_alloc(n, nobjs);
  33.                 obj** my_free_list;
  34.                 obj* result;
  35.                 char* current_obj, * next_obj;
  36.                 int i;
  37.                 if (1 == nobjs)
  38.                         return chunk;
  39.                 my_free_list = free_list + FREELIST_INDEX(n);
  40.                 result = (obj*)chunk;
  41.                 *my_free_list = next_obj = (obj*)(chunk + n);
  42.                 for (i = 1;; i++)//把free list各个节点串起来
  43.                 {
  44.                         current_obj = next_obj;
  45.                         next_obj = (obj*)((char*)next_obj + n);
  46.                         if (nobjs - 1 == i)
  47.                         {
  48.                                 current_obj->free_list_link = 0;
  49.                                 break;
  50.                         }
  51.                         else
  52.                         {
  53.                                 current_obj->free_list_link = next_obj;
  54.                         }
  55.                 }
  56.                 return result;
  57.         }
  58.         //配置一大块空间,可以容纳nogjs个大小为size的区块
  59.         //内存池
  60.         static char* chunk_alloc(size_t size, int& nobjs)
  61.         {
  62.                 char* result;
  63.                 size_t total_bytes = size * nobjs;  //8*20
  64.                 size_t bytes_left = end_free - start_free;
  65.                 if (bytes_left >= total_bytes)
  66.                 {//内存池剩余空间完全满足需求量
  67.                         result = start_free;
  68.                         start_free += total_bytes;
  69.                         return result;
  70.                 }
  71.                 else if (bytes_left >= size)
  72.                 {//不完全满足,但够一个以上的区块
  73.                         nobjs = bytes_left / size;
  74.                         total_bytes = size * nobjs;
  75.                         result = start_free;
  76.                         start_free += total_bytes;
  77.                         return result;
  78.                 }
  79.                 else
  80.                 {//一个区块也无法提供
  81.                         //320
  82.                         size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
  83.                         if (bytes_left > 0)
  84.                         {
  85.                                 obj** my_free_list = free_list + REFFLIST_INDEX(bytes_left);
  86.                                 ((obj*)start_free)->free_list_link = *my_free_list;
  87.                                 *my_free_list = (obj*)start_free;
  88.                         }
  89.                         start_free = (char*)malloc(bytes_to_get);
  90.                         if (0 == start_free)
  91.                         {
  92.                                 int i;
  93.                                 obj** my_free_list, * p;
  94.                                 for (i = size; i <= __MAX_BYTES; i += __ALIGN)
  95.                                 {
  96.                                         my_free_list = free_list + FREELIST_INDEX(i);
  97.                                         if (0 != p)
  98.                                         {
  99.                                                 *my_free_list = p->free_list_link;
  100.                                                 start_free = (char*)p;
  101.                                                 end_free = start_free + i;
  102.                                                 return chunk_alloc(size, nobjs);
  103.                                         }
  104.                                 }
  105.                                 end_free = 0;
  106.                                 start_free = (char*)malloc_alloc::allocate(bytes_to_get);
  107.                         }
  108.                         heap_size += bytes_to_get;
  109.                         end_free = start_free + bytes_to_get;
  110.                         return (chunk_alloc(size, nobjs));
  111.                 }
  112.         }
  113.         static char* start_free;//内存池起始位置
  114.         static char* end_free;//内存池结束位置
  115.         static size_t heap_size;
  116. public:
  117.         //空间申请的接口
  118.         //首先判断区块的大小,如果大于 128bytes –> 调用第一级配置器;
  119.         //小于128bytes–> 就检查对应的 free_list(如果没有可用区块,就将区块上调至 8 倍数的边界,
  120.         //然后调用 refill(), 为 free list 重新填充空间。
  121.         static void* allocate(size_t n)
  122.         {
  123.                 obj** my_free_list;
  124.                 obj* result;
  125.                 if (n > (size_t)__MAX_BYTES)  //如果>128 调用一级
  126.                 {
  127.                         return (malloc_alloc::allocate(n));
  128.                 }
  129.                 //寻找16个free list的适合的一个
  130.                 my_free_list = free_list + FREELIST_INDEX(n);
  131.                 result = *my_free_list;
  132.                 if (result == 0)
  133.                 {//没有找到可用的,准备重新填充free list
  134.                         void* r = refill(ROUND_UP(n));
  135.                         return r;
  136.                 }
  137.                 *my_free_list = result->free_list_link;
  138.                 return result;
  139.         }
  140.         //释放空间
  141.         static void deallocate(void* p, size_t n)
  142.         {
  143.                 obj* q = (obj*)p;
  144.                 obj* volatile* my_free_list;
  145.                 if (n > (size_t)__MAX_BYTES)
  146.                 {
  147.                         malloc_alloc::deallocate(p, n);
  148.                         return;
  149.                 }
  150.         }
  151.         static void* reallocate(void* p, size_t old_sz, size_t new_sz);
  152. };
  153. template<bool threads, int inst>
  154. char* __default_alloc_template<threads, inst>::start_free = 0;
  155. template<bool threads, int inst>
  156. char* __default_alloc_template<threads, inst>::end_free = 0;
  157. template<bool threads, int inst>
  158. size_t __default_alloc_template<threads, inst>::heap_size = 0;
  159. template<bool threads, int inst>
  160. __default_alloc_template<threads, inst>::obj*
  161. __default_alloc_template<threads, inst>::free_list[__NFREELISTS] = { 0,0,0,0,0,0 }
复制代码
 推荐阅读:_herongweiV的博客-CSDN博客
二级空间设置器的原理剖析和简单实现_ 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表