不落辰

知不可乎骤得,托遗响于悲风

0%

轮子-sgi_stl二级空间配置器移植代码

轮子-sgi_stl二级空间配置器移植

sgi_stl 内存池

图示

  • template<typename T> class my_allocator{}

OOP封装

  • 对外接口
    • allocate、deallocate、reallocate、construct、destroy
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      174
      175
      176
      177
      178
      179
      180
      181
      182
      183
      184
      185
      186
      187
      188
      189
      190
      191
      192
      193
      194
      195
      196
      197
      198
      199
      200
      201
      202
      203
      204
      205
      206
      207
      208
      209
      210
      211
      212
      213
      214
      215
      216
      217
      218
      219
      220
      221
      222
      223
      224
      225
      226
      227
      228
      229
      230
      231
      232
      233
      234
      235
      236
      237
      238
      239
      240
      241
      242
      243
      244
      245
      246
      247
      248
      249
      250
      251
      252
      253
      254
      255
      256
      257
      258
      259
      260
      261
      262
      263
      264
      265
      266
      267
      268
      269
      270
      271
      272
      273
      274
      275
      276
      277
      278
      279
      280
      281
      282
      283
      284
      285
      286
      287
      288
      289
      290
      291
      292
      293
      294
      295
      296
      297
      298
      299
      300
      301
      302
      303
      304
      305
      306
      307
      308
      309
      310
      311
      312
      313
      314
      315
      316
      317
      318
      319
      320
      321
      322
      323
      324
      325
      326
      327
      328
      329
      330
      331
      332
      333
      334
      335
      336
      337
      338
      339
      340
      341
      342
      343
      344
      345
      346
      347
      348
      349
      350
      351
      352
      353
      354
      355
      356
      357
      358
      359
      360
      361
      362
      363
      364
      365
      366
      367
      368
      369
      370
      371
      372
      373
      374
      375
      376
      377
      378
      379
      380
      381
      382
      383
      384
      385
      386
      387
      388
      389
      390
      391
      392
      393
      394
      395
      396
      397
      398
      399
      400
      401
      402
      403
      404
      405
      406
      407
      408
      409
      410
      411
      412
      #include<cstdlib>
      #include<mutex>
      #include<memory>
      #include<iostream>
      /*
      * 移植SGI STL二级空间配置器源码
      * 不同于nginx内存池,二级空间配置器中使用到的内存池需要注意多线程问题
      * nginx内存池,可以一个线程创建一个内存池
      * 而二级空间配置器,是用来给容器使用的,如vector对象。而一个vector对象很有可能在多个线程并发使用
      *
      *
      * 移植成功!一个线程安全的二级空间配置器 my_allocator
      */
      // 一级空间配置器 将对象构造和内存开辟分开
      template<typename T>
      class first_level_my_allocator
      {
      public:
      T* allocate(size_t size)
      {
      T* p = malloc(sizeof(T)*size);
      return p;
      }

      void deallocate(T *p)
      {
      free(p);
      }

      void construct(T* p, const T& _val)
      {
      new (p) T(_val);
      }

      void construct(T* p, T&& rval)
      {
      new (p) T(std::move(rval));
      }

      void destory(T* p)
      {
      p->~T();
      }
      };


      template <int __inst>
      class __malloc_alloc_template {

      private:
      static void* _S_oom_realloc(void*, size_t);
      // 预制的回调函数 _S_oom_malloc -> __malloc_alloc_oom_handler
      static void* _S_oom_malloc(size_t);
      static void (*__malloc_alloc_oom_handler)();
      public:
      // 尝试分配内存
      static void* allocate(size_t __n)
      {
      void* __result = malloc(__n);
      if (0 == __result) __result = _S_oom_malloc(__n); // 尝试释放nbytes内存,返回给result
      return __result;
      }

      // 释放
      static void deallocate(void* __p, size_t /* __n */)
      {
      free(__p);
      }

      // 重新分配new_sz大小内存
      static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz)
      {
      void* __result = realloc(__p, __new_sz);
      if (nullptr == __result) __result = _S_oom_realloc(__p, __new_sz);
      return __result;
      }

      // 用户通过这个接口来预制自己的回调函数。用以释放内存来解决内存不足的问题
      static void (*__set_malloc_handler(void (*__f)()))()
      {
      void (*__old)() = __malloc_alloc_oom_handler;
      __malloc_alloc_oom_handler = __f;
      return(__old);
      }

      };


      // my_allocator中用到的__malloc_alloc_template中的两个函数和一个成员
      template <int __inst>
      void*
      __malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
      {
      void (*__my_malloc_handler)();
      void* __result;

      for (;;) {
      __my_malloc_handler = __malloc_alloc_oom_handler;
      if (nullptr == __my_malloc_handler) { throw std::bad_alloc(); }
      (*__my_malloc_handler)();
      __result = malloc(__n);
      if (__result) return(__result);
      }
      }


      template <int __inst>
      void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
      {
      void (*__my_malloc_handler)();
      void* __result;

      for (;;) {
      __my_malloc_handler = __malloc_alloc_oom_handler;
      if (nullptr == __my_malloc_handler) { throw std::bad_alloc(); }
      (*__my_malloc_handler)();
      __result = realloc(__p, __n);
      if (__result) return(__result);
      }
      }

      template <int __inst>
      void (*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = nullptr;
      using malloc_alloc = __malloc_alloc_template<0>;




      // 二级空间配置器 对外接口为allocate、deallocate、reallocate、construct、destroy
      template<typename T>
      class my_allocator
      {
      public:
      using value_type = T;
      using _Newfirst = T;
      using _From_primary = my_allocator;
      constexpr my_allocator() noexcept {}
      constexpr my_allocator(const my_allocator&) noexcept = default;
      template <class _Other>
      constexpr my_allocator(const my_allocator<_Other>&) noexcept {}

      // 开辟内存
      T* allocate(size_t __n);

      // 释放内存
      void deallocate(void* __p, size_t __n);

      // 重新分配内存。并将原先的内存归还给操作系统。并且不要再使用p的原指向地址
      void* reallocate(void* __p, size_t __old_sz, size_t __new_sz);

      // 构建对象
      void construct(T* __p, const T& val)
      {
      new (__p) T(val);
      }
      void construct(T* __p, T&& val)
      {
      new (__p) T(std::move(val));
      }

      // 释放内存
      void destroy(T* __p)
      {
      __p->~T();
      }

      private:
      // Really we should use static const int x = N
      // instead of enum { x = N }, but few compilers accept the former.
      enum { _ALIGN = 8 }; // 自由链表从8bytes开始,以8bytes为对齐方式,一直扩充到128Bytes
      enum { _MAX_BYTES = 128 }; // 最大块大小 >128就不会放到内存池里了,也即不会用二级空间配置器。会用一级空间配置器
      enum { _NFREELISTS = 16 }; // 自由链表成员个数 _MAX_BYTES/_ALIGN

      // 将byte上调至8的倍数
      static size_t _S_round_up(size_t __bytes)
      {
      return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
      }

      // 计算出bytes大小的chunk应该挂载到自由链表free-list的哪个成员下
      static size_t _S_freelist_index(size_t __bytes) {
      return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
      }

      // 开辟内存池,挂载到freelist成员下,返回请求的内存块。
      static void* _S_refill(size_t __n);
      // 从还没形成链表的原始内存中取内存分配给自由链表成员,去形成链表。如果原始空闲内存不够了,则再开辟
      static char* _S_chunk_alloc(size_t __size, int& __nobjs);


      // 每个chunk块的信息。_M_free_list_link指向下一个chunk块
      union _Obj {
      union _Obj* _M_free_list_link;
      char _M_client_data[1]; /* The client sees this. */
      };

      // 基于free-list的内存池,需要考虑线程安全
      static _Obj* volatile _S_free_list[_NFREELISTS]; // 防止被线程缓存
      static std::mutex mtx;


      // static:类内声明、类外定义。
      static char* _S_start_free; // 空闲free内存的起始start位置
      static char* _S_end_free; // 空闲free内存的结束end位置 )
      static size_t _S_heap_size; // 总共malloc过的内存大小(因为malloc是从堆heap上请求的,所以叫heapsize)

      };


      // static 类内声明,类外定义/初始化
      template<typename T>
      std::mutex my_allocator<T>::mtx;

      template <typename T>
      char* my_allocator<T>::_S_start_free = 0;
      template <typename T>
      char* my_allocator<T>::_S_end_free = 0;
      template <typename T>
      size_t my_allocator<T>::_S_heap_size = 0;

      template <typename T>
      typename my_allocator<T>::_Obj* volatile
      my_allocator<T> ::_S_free_list[my_allocator<T>::_NFREELISTS]
      = { nullptr,nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, };



      template<typename T>
      T* my_allocator<T>::allocate(size_t __n)
      {
      __n = __n * sizeof(T); // 因为vector容器传来的是元素个数
      // std::cout << "user allocate " << __n << std::endl;
      void* __ret = 0;
      if (__n > (size_t)_MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
      }
      else {
      _Obj* volatile* __my_free_list
      = _S_free_list + _S_freelist_index(__n);
      // Acquire the lock here with a constructor call.
      // This ensures that it is released in exit or during stack
      // unwinding.
      // _S_free_list 是所有线程共享的。加锁实现线程安全
      std::lock_guard<std::mutex> guard(mtx);

      _Obj* __result = *__my_free_list;
      if (__result == 0)
      __ret = _S_refill(_S_round_up(__n));
      else {
      *__my_free_list = __result->_M_free_list_link;
      __ret = __result;
      }
      }
      return static_cast<T*>(__ret);
      }



      template<typename T>
      void* my_allocator<T>::_S_refill(size_t __n) // n是一个chunk块的大小
      {
      // 分配指定大小的内存池 __nobjs:chunk内存块数量 ;这里的 __n:每个chunk内存块大小。
      int __nobjs = 20;
      char* __chunk = _S_chunk_alloc(__n, __nobjs);
      _Obj* volatile* __my_free_list;
      _Obj* __result;
      _Obj* __current_obj;
      _Obj* __next_obj;
      int __i;
      // __nobjs:申请到的chunk块数量。当只申请到一个时,直接返回该内存块给上一级使用。无需建立各个chunk的连接关系,无需挂载到相应的freelist成员下。(因为只有一个)
      if (1 == __nobjs) return(__chunk);

      __my_free_list = _S_free_list + _S_freelist_index(__n); // 根据内存块大小求出内存池应该在由freelist第几个成员管理(指向)

      // 静态链表:把每个chunk块通过Obj*里的指针连接起来
      // 每个内存块,有一部分的内存时union联合体Obj,里面有一个Obj*指针,负责连接每个空闲内存块。
      /* Build free list in chunk */
      __result = (_Obj*)__chunk;
      *__my_free_list = __next_obj = (_Obj*)(__chunk + __n); // __n:一个内存块的大小(因为第一个内存块要分配出去)
      for (__i = 1; ; __i++) {
      __current_obj = __next_obj;
      __next_obj = (_Obj*)((char*)__next_obj + __n); // 维护内存块间的连接 char* 因此+__n是偏移n个bytes +n是为了一次跑一个chunk块
      if (__nobjs - 1 == __i) { // 空闲内存块数为0?
      __current_obj->_M_free_list_link = 0;
      break;
      }
      else {
      __current_obj->_M_free_list_link = __next_obj;
      }
      }
      return(__result); // 返回第一个内存块
      }


      /* We allocate memory in large chunks in order to avoid fragmenting */
      /* the malloc heap too much. */
      /* We assume that size is properly aligned. */
      /* We hold the allocation lock. */
      template <typename T>
      char* my_allocator<T>::_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; // __default_alloc_template<__threads, __inst> 从开始到现在,请求的剩余空闲的的内存大小。不包括回收的。只是开辟的。

      if (__bytes_left >= __total_bytes) { // 剩余的备用内存够支付本次请求的内存大小。
      __result = _S_start_free; // __result 作为返回内存首地址
      _S_start_free += __total_bytes; // 移动_S_start_free
      return(__result); // [result , _S_start_free) 作为请求结果返回
      }
      else if (__bytes_left >= __size) { // 剩余的不够支付total,但起码能支付一个内存块。(因为要返回的至少是一个内存块大小)
      __nobjs = (int)(__bytes_left / __size);
      __total_bytes = __size * __nobjs;
      __result = _S_start_free;
      _S_start_free += __total_bytes;
      return(__result);
      }
      else { // 剩余的free内存连一个内存块也不够支付
      size_t __bytes_to_get = // 当剩余的空闲内存不够时,需要向操统malloc内存。这是计算出需要malloc内存的大小(至少malloc出来要求内存的(__total_bytes)两倍)
      2 * __total_bytes + _S_round_up(_S_heap_size >> 4);
      // Try to make use of the left-over piece.
      // 剩余的备用内存bytes_left,又不够本次请求的一个chunk块大小,就把这块内存挂载到他能所属的freelist成员下。(头插法)
      if (__bytes_left > 0) { //
      _Obj* volatile* __my_free_list =
      _S_free_list + _S_freelist_index(__bytes_left);

      ((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;
      *__my_free_list = (_Obj*)_S_start_free;
      }
      _S_start_free = (char*)malloc(__bytes_to_get); // 向操统malloc内存
      if (nullptr == _S_start_free) { // malloc失败
      size_t __i;
      _Obj* volatile* __my_free_list;
      _Obj* __p;
      // Try to make do with what we have. That can't
      // hurt. We do not try smaller requests, since that tends
      // to result in disaster on multi-process machines.
      // 从别的freelist成员管理的原始备用内存池中借用至少size大小的chunk块
      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 (nullptr != __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));
      // Any leftover piece will eventually make it to the
      // right free list.
      }
      }
      // 都没有时,只能allcoate
      _S_end_free = nullptr; // In case of exception.
      _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
      // This should either throw an
      // exception or remedy the situation. Thus we assume it
      // succeeded.
      }
      _S_heap_size += __bytes_to_get; // _S_heap_size:迄今为止总共malloc了多少内存?
      _S_end_free = _S_start_free + __bytes_to_get; // 移动_S_end_free指针。指向空闲内存块末尾
      return(_S_chunk_alloc(__size, __nobjs)); // 递归调用
      }
      }


      /* __p may not be 0 */
      template<typename T>
      void my_allocator<T>::deallocate(void* __p, size_t __n) // 因为vector容器传来的是字节大小
      {
      if (__n > (size_t)_MAX_BYTES) // n>128 同一级空间配置器
      {
      malloc_alloc::deallocate(__p, __n);
      }
      else
      {
      _Obj* volatile* __my_free_list
      = _S_free_list + _S_freelist_index(__n); // 找到相应freelist成员
      _Obj* __q = (_Obj*)__p; //

      // acquire lock
      std::lock_guard<std::mutex> guard(mtx);

      __q->_M_free_list_link = *__my_free_list;
      *__my_free_list = __q;
      // lock is released here
      }
      }




      template<typename T>
      void* my_allocator<T>::reallocate(void* __p, size_t __old_sz, size_t __new_sz)
      {
      void* __result;
      size_t __copy_sz;

      // old new都>128byets,那么用的就应当是和一级空间配置器一样的方法malloc
      if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {
      return(realloc(__p, __new_sz));
      }

      // 如果即将分配的chunk块大小一致,那么不必,直接返回
      if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);

      // 重新开辟new_size大小内存,并拷贝数据到新开辟的内存里
      __result = allocate(__new_sz);
      __copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;
      memcpy(__result, __p, __copy_sz); // 拷贝数据
      deallocate(__p, __old_sz); // 释放
      return(__result);
      }

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<vector>
#include"myallocator.h"

int main()
{
std::vector<int,my_allocator<int>> vec;
for (int i = 0; i < 100; ++i)
{
vec.push_back(i);
}

for (auto x : vec) {
std::cout << x << std::endl;
}
}

shc@shc-virtual-machine:~/code/sgi_stl$ g++ *.cpp -o sgi_stl_allocator.out -Wall
shc@shc-virtual-machine:~/code/sgi_stl$ ./sgi_stl_allocator.out
0
...
98
99


二级空间配置器总结:
在一级空间配置器的基础上 增加了 基于 freelist 实现的内存池
freelist的每个成员下面挂了一链大小相等的内存块。freelist0挂8byte,freelist1挂16byte诸如此类
当user allocate的时候 就会根据alloc的大小(比如说32bytes)到相应的freelist成员下面去取出内存块。(避免了遍历链表寻找内存块,且可以提供一定程度的并发)
如果此时该成员(3)下有一32bytes块,那么取下该块,并返回给user
如果没有,那么耳机空间配置器向下(向原始内存)请求,20个32bytes块。
插一句,我们可以将这个二级空间配置器的内存,分为三部分:一部分是已经返回给user使用的内存,一部分是挂在freelist之下的、形成链表的内存块,另一部分是:还没挂在freelist之下的内存块(称为原始内存吧先)
原始内存尽可能多的给我们返回的32bytes块,如果原始内存中已经连一个32bytes都没有的时候,原始内存通过malloc向os请求内存。请求内存的大小为20322。注意这个2,每次多申请一倍,减少syscall。(这也就是为什么会有原始内存)
如果malloc失败,则抛出异常。malloc成功,则此时原始内存有new mem(20
32*2) + old left mem(less than 32),于是返回20个32bytes给之前向我们请求20个32bytes块的freelist成员;且,很巧妙的,为榨干所有内存,原始内存又为old left mem,找到其可以挂到的freelist成员下,将其挂在下面–这样就成功利用了每一寸内存。再说之前返回的20个32bytes,freelist成员(3) 将其串成链表,挂在自己之下,然后向user返回一个32bytes块。结束。

那么说说优点
freelist 相同大小内存块放在一个freelist[i]成员下,避免了遍历链表寻找合适内存块的开销,直接定位到在哪个freelist[i]下了;且可以提供一定的并发性。
对于每一个字节数的chunk块分配,都是返回给用户一个使用的内存块,并给freelist一个内存池,,并留有另一部分作为备用内存池。这个备用可以给当前的字节数freelist成员使用,也可以给其他字节数的freelist成员使用。
对于原始内存之前剩余的小块内村,也不会浪费,会将其挂到相应成员下。

缺点:因为不会归还内存给os,所以可能耗尽内存资源。(user通过二级空间配置其的释放动作只会将内存块挂到freelist的成员下)
二级空间配置器是在堆上申请大块的狭义内存池,然后用自由链表管理,供现在使用,在程序执行过 程中,它将申请的内存一块一块都挂在自由链表上,即不会还给操作系统,并且它的实现中所有成员全 是静态的,所以它申请的所有内存只有在进程结束才会释放内存,还给操作系统,由此带来的问题有:
1.即我不断的开辟小块内存,最后整个堆上的空间都被挂在自由链表上,若我想开辟大块内存就会失 败;
2.若自由链表上挂很多内存块没有被使用,当前进程又占着内存不释放,这时别的进程在堆上申请 不到空间,也不可以使用当前进程的空闲内存,由此就会引发多种问题。