"Analysis of STL Source Code" (2) - Space Configurator

1. Why have a space configurator:

1. The problem of memory fragmentation caused by small memory

From the perspective of memory allocation alone, due to frequent allocation and release of small blocks of memory, it is easy to cause external fragmentation in the heap (in extreme cases: the total amount of free space in the heap meets a requirement, but these free blocks are not continuous, resulting in any A single free block cannot satisfy the request)

2. Performance problems caused by frequent application and release of small blocks of memory:

(1) When opening up space, the allocator will find a free block for the user. It takes time to find a free block, especially when there are many external fragments. If the allocator cannot be found, the phenomenon of false fragmentation should be considered (the released small blocks of memory are not merged). At this time, these free blocks that have been released must be merged, which also takes time.
(2) When malloc opens up space, these spaces will carry some additional information, which will also reduce the utilization of space, especially when small blocks of memory are frequently requested.

In order to solve the above problems, STL proposed the concept of memory pool. The most basic idea of ​​memory pool is to apply for a large piece of memory (memory pool) from heap at a time. If you apply for a small piece of memory, go directly to the memory pool and ask for it, which is an effective solution. above problem.

2. STL's space configurator (SGI version)

STL's space configurator is divided into two levels, the first-level space configurator (__malloc_alloc_template) and the second-level space configurator (__default_alloc_template). In STL, if the memory allocated by default is greater than 128, call the first-level space configurator to apply for memory; if it is less than or equal to 128 bytes, it is considered as a small block of memory, and go to the memory pool to apply for memory. The first-level space configurator is relatively simple, directly encapsulating malloc() and free() processing.

1. First-level space configuratorinsert image description here

(1) malloc() applies for memory.
(2) After malloc() fails, call oom_malloc():
a If the client does not set the insufficient memory handling mechanism, throw bad_alloc.
b The insufficient memory mechanism is set, and the memory processing mechanism is called until a sufficient memory is obtained. Improper setting of the memory processing mechanism will cause an infinite loop problem.

typedef void(*MALLOCALLOC)();           //将void (*)()   重命名成MALLOCALLOC
template<int inst>
class _MallocAllocTemplate
{
    
    
private:
       static void* _OomMalloc(size_t);       //malloc失败的时候调用的函数
       static MALLOCALLOC _MallocAllocOomHandler;         //函数指针,内存不足的时候的处理机制
public:
       static void* _Allocate(size_t n)                        //分配空间n个字节的空间
       {
    
    
              void *result=0;
              result = malloc(n);
              if (0 == result)                    //若果malloc失败,则就调OOM_malloc
                     _OomMalloc(n);
              return result;
       }
       static void _DeAllocate(void *p)                //释放这块空间
       {
    
    
              free(p);
       }
       static MALLOCALLOC _SetMallocHandler(MALLOCALLOC f)    //这是一个函数,参数是一个函数指针,返回值也是一个函数指针
       {
    
    
              MALLOCALLOC old = _MallocAllocOomHandler;
              _MallocAllocOomHandler = f;              //将内存分配失败的句柄设置为f(让它指向一个内存失败了,让系统去释放其他地方空间的函数)
              return old;
       }
};
template<int inst>
void(* _MallocAllocTemplate<inst>::_MallocAllocOomHandler)()=0;    //默认不设置内存不足处理机制
template<int inst>
void* _MallocAllocTemplate<inst>::_OomMalloc(size_t n)
{
    
    
       MALLOCALLOC _MyMallocHandler;     //定义一个函数指针
       void *result;               
       while (1)
       {
    
    
              _MyMallocHandler = _MallocAllocOomHandler;
              if (0 == _MyMallocHandler)                  //没有设置内存不足处理机制
                     throw std::bad_alloc();                  //则抛出异常
              (*_MyMallocHandler)();                 //调用内存不足处理的函数,申请释放其他地方的内存
              if (result = malloc(n))                //重新申请内存
                     break;
       }
       return result;                              //申请成功时,则返回这块内存的地址
}
typedef _MallocAllocTemplate<0> malloc_alloc;

2. Secondary space configurator

The secondary space configurator uses the form of memory pool + free linked list to avoid fragmentation caused by small blocks of memory, improve allocation efficiency, and improve utilization. Assuming that an 8-byte space is allocated, he will go to the memory pool to allocate multiple 8-byte memory blocks, and the excess will be hung on the free linked list. The next time you need 8 bytes, you can go to the free linked list to get it. If 8 bytes are recovered, it is directly linked to the free linked list.
In order to facilitate management, the secondary space configurator is aligned in multiples of 8 when allocating. In this way, you only need to maintain 16 free_lists, and the 16 nodes of the free_list manage memory with a size of 8, ... 128 bytes respectively.
Node type of free linked list:

union _Obj
{
    
    
       union _Obj* _M_free_list_link;
       char _M_client_data[1];    /* The client sees this.        */
};

Memory pool model:
insert image description here
In order to connect the nodes of the free linked list without introducing additional pointers, the smallest memory block hanging under the free linked list is 8 bytes, which is enough to store one address, so other memory blocks can be stored in these blocks. The address of the memory block, that is, the memory block is connected.
Class for the secondary space configurator:

enum {
    
     _ALIGN = 8 };              //按照基准值8的倍数进行内存操作
enum {
    
     _MAXBYTES = 128 };        //自由链表中最大的块的大小是128
enum {
    
     _NFREELISTS = 16 };       //自由链表的长度,等于_MAXBYTES/_ALIGN
template <bool threads, int inst>  //非模板类型参数
class _DefaultAllocTemplate
{
    
    
       union _Obj                      //自由链表结点的类型
       {
    
    
              _Obj* _freeListLink;         //指向自由链表结点的指针
              char _clientData[1];          //this client sees
       };
private:
       static char* _startFree;             //内存池的头指针
       static char* _endFree;               //内存池的尾指针
       static size_t _heapSize;              //记录内存池已经向系统申请了多大的内存
       static _Obj* volatile _freeList[_NFREELISTS];    //自由链表
private:
       static size_t _GetFreeListIndex(size_t bytes)   //得到这个字节对应在自由链表中应取的位置
       {
    
    
              return (bytes +(size_t) _ALIGN - 1) / (size_t)_ALIGN - 1;     
       }
       static size_t _GetRoundUp(size_t bytes)        //对这个字节向上取成8的倍数
       {
    
    
              return (bytes + (size_t)_ALIGN - 1)&(~(_ALIGN-1));     //将n向上取成8的倍数
       }
       static void* _Refill(size_t n);          //在自由链表中申请内存,n表示要的内存的大小
       static char* _chunkAlloc(size_t size,int& nobjs);    //在内存池中申请内存nobjs个对象,每个对象size个大小
public:
       static void* Allocate(size_t n);      //n要大于0
       static void DeAllocate(void *p,size_t n);        //n要不等于0
};

Suppose you apply for n (<= 128) bytes:
1. Increase n to a multiple of 8 and find it under the corresponding node of the free linked list. If there is unused memory under the node, take it off and return to this space the address of. Otherwise, call refill() to request memory from the memory pool.
2. When applying to the memory pool, apply for a few more. STL applies for nobjs=20 at a time by default, and hangs the excess on the free linked list, which can improve efficiency.
After entering the refill function, first call the chunk_alloc(n,nobjs) function to apply for memory in the memory pool. If successful, return to the refill function.
If nobjs=1, it means that the memory pool is only enough to allocate one, then this address is returned, otherwise, it means that nobjs is greater than 1, and the extra memory block is hung on the free linked list.
3. There are three situations when entering chunk_alloc(n,nobjs):
3.1 The remaining space is enough, so directly allocate and return.
3.2 The remaining space is enough for n, but not enough for n*nobjs, at this time nobjs=remaining space/n, return.
3.3 If the remaining space in the memory pool is not enough n, apply for memory from the heap. Before applying, hang the remaining memory in the memory pool on the free linked list, and then apply to the heap.
3.3.1 If the application is successful, call a chunk—alloc() to re-allocate.
3.3.2 If it is unsuccessful, go to the free linked list to see if there is space greater than n. If there is, return it to the memory pool and call chunk_alloc to reallocate it.
3.3.3 If not, call the level 1 allocator to see if there is an insufficient memory handling mechanism.
insert image description here

enum {
    
     _ALIGN = 8 };              //按照基准值8的倍数进行内存操作
enum {
    
     _MAXBYTES = 128 };        //自由链表中最大的块的大小是128
enum {
    
     _NFREELISTS = 16 };       //自由链表的长度,等于_MAXBYTES/_ALIGN
template <bool threads, int inst>  //非模板类型参数
class _DefaultAllocTemplate
{
    
    
       union _Obj                      //自由链表结点的类型
       {
    
    
              _Obj* _freeListLink;         //指向自由链表结点的指针
              char _clientData[1];          //this client sees
       };
private:
       static char* _startFree;             //内存池的头指针
       static char* _endFree;               //内存池的尾指针
       static size_t _heapSize;              //记录内存池已经向系统申请了多大的内存
       static _Obj* volatile _freeList[_NFREELISTS];    //自由链表
private:
       static size_t _GetFreeListIndex(size_t bytes)   //得到这个字节对应在自由链表中应取的位置
       {
    
    
              return (bytes +(size_t) _ALIGN - 1) / (size_t)_ALIGN - 1;     
       }
       static size_t _GetRoundUp(size_t bytes)        //对这个字节向上取成8的倍数
       {
    
    
              return (bytes + (size_t)_ALIGN - 1)&(~(_ALIGN-1));     //将n向上取成8的倍数
       }
       static void* _Refill(size_t n);          //在自由链表中申请内存,n表示要的内存的大小
       static char* _chunkAlloc(size_t size,int& nobjs);    //在内存池中申请内存nobjs个对象,每个对象size个大小
public:
       static void* Allocate(size_t n);      //n要大于0
       static void DeAllocate(void *p,size_t n);        //n要不等于0
};
template<bool threads,int inst>
char* _DefaultAllocTemplate<threads,inst>::_startFree = 0;        //内存池的头指针
template<bool threads, int inst>
char* _DefaultAllocTemplate<threads, inst>::_endFree=0;           //内存池的尾指针
template<bool threads, int inst>
size_t _DefaultAllocTemplate<threads, inst>::_heapSize = 0;              //记录内存池已经向系统申请了多大的内存
template<bool threads, int inst>
typename _DefaultAllocTemplate<threads, inst>::_Obj* volatile      //前面加typename表示后面是个类型
_DefaultAllocTemplate<threads, inst>::_freeList[_NFREELISTS] = {
    
    0};    //自由链表
 
template<bool threads, int inst>
void* _DefaultAllocTemplate<threads, inst>::Allocate(size_t n)    //分配空间
{
    
    
       void *ret;
       //先判断要分配的空间大小是不是大于128个字节
       if (n>_MAXBYTES)      //大于_MAXBYTES个字节则认为是大块内存,直接调用一级空间配置器
       {
    
    
              ret = malloc_alloc::_Allocate(n);
       }
       else       //否则就去自由链表中找
       {
    
    
              _Obj* volatile *myFreeList = _freeList+_GetFreeListIndex(n);  //让myFreeList指向自由链表中n向上取8的整数倍
              _Obj* result = *myFreeList;
              if (result == 0)  //这个结点下面没有挂内存,则就要去内存池中申请
              {
    
    
                     ret = _Refill(_GetRoundUp(n));      //到内存池中申请
              }
              else            //已经在自由链表上找到了内存
              {
    
    
                     *myFreeList= result->_freeListLink;      //把第二块空间的地址放到自由链表上
                     ret = result;
              }
       }
       return ret;
}
template<bool threads, int inst>
void _DefaultAllocTemplate<threads, inst>::DeAllocate(void *p, size_t n)   //回收空间
{
    
    
       //先判断这个字节的大小
       if (n > _MAXBYTES)  //如果n大于自由链表中结点所能挂的最大内存块,则就直接调用一级指针的释放函数
       {
    
    
              malloc_alloc::_DeAllocate(p);
       }
       else        //将这块内存回收到自由链表中
       {
    
    
              _Obj* q = (_Obj*)p;
              _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(n);
              q->_freeListLink = *myFreeList;
              *myFreeList = q;
       }
}
 
template<bool threads,int inst>
void* _DefaultAllocTemplate<threads, inst>::_Refill(size_t n)     //n表示要申请的字节个数
{
    
    
       int nobjs = 20;           //向内存池申请的时候一次性申请20个
       char* chunk = _chunkAlloc(n,nobjs);    //因为现在链表中没有,所以要想内存池中申请,多余的再挂到自由链表下面
       if (1 == nobjs)          //只分配到了一个对象
       {
    
    
              return chunk;
       }
       _Obj* ret = (_Obj*)chunk;                  //将申请的第一个对象作为返回值
       _Obj* volatile *myFreeList = _freeList+ _GetFreeListIndex(n);
       *myFreeList =(_Obj*)(chunk+n);             //将第二个对象的地址放到自由链表中
       _Obj* cur= *myFreeList;
       _Obj* next=0;
       cur->_freeListLink = 0;
       for (int i = 2; i < nobjs; ++i)             //将剩下的块挂到自由链表上
       {
    
    
              next= (_Obj*)(chunk + n*i);
              cur->_freeListLink = next;
              cur = next;
       }
       cur->_freeListLink = 0;
       return ret;
}
template<bool threads, int inst>
char* _DefaultAllocTemplate<threads, inst>::_chunkAlloc(size_t size, int& nobjs)  //向系统中申请内存
{
    
    
       char* result = 0;
       size_t totalBytes = size*nobjs;        //总共请求的内存大小
       size_t leftBytes = _endFree - _startFree;      //内存池剩余的大小
       if (leftBytes>=totalBytes)     //如果剩余的大小大于等于申请的大小,则返回这个这内存
       {
    
    
              result = _startFree;
              _startFree += totalBytes;
              return result;
       }
       else if (leftBytes>size)         //如果剩余的内存足够分配一个size,
       {
    
    
              nobjs=(int)(leftBytes/size);
              result = _startFree;
              _startFree +=(nobjs*size);
              return result;
       }
       else            //内存池中的内存已经不够一个size了
       {
    
    
              size_t NewBytes = 2 * totalBytes+_GetRoundUp(_heapSize>>4);       //内存池要开辟的新的容量
              if (leftBytes >0)  //剩余的内存挂到自由链表上
              {
    
    
                     _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(leftBytes);
                     ((_Obj*)_startFree)->_freeListLink = *myFreeList;
                     *myFreeList = (_Obj*)_startFree;
              }
              
              //开辟新的内存
              _startFree = (char*)malloc(NewBytes);
              if (0 == _startFree)                   //如果开辟失败
              {
    
    
                     //如果开辟失败的话,则表明系统已经没有内存了,这时候我们就要到自由链表中找一块比n还大的内存块,如果还没有的话,那就掉一级空间配置器
                     for (size_t i = size; i <(size_t)_MAXBYTES;i+=(size_t)_ALIGN)
                     {
    
    
                           _Obj* volatile *myFreeList = _freeList + _GetFreeListIndex(i);
                           _Obj* p =*myFreeList;
                           if (NULL != p)       //在自由链表找到一块内存块
                           {
    
    
                                  _startFree =(char*)p;                  
                                  //将这个内存块摘下来给内存池
                                  *myFreeList = p->_freeListLink;
                                  _endFree = _startFree + i;
                                  return _chunkAlloc(size, nobjs);  //内存池开辟好的话,就再调一次chunk分配内存
                           }
                     }
                     //要是再找不到的话,就调一级空间配置器,其中有内存不足处理机制,要是还不行的话,他会自动抛出异常
                     _endFree = NULL;
                     _startFree=(char*)malloc_alloc::_Allocate(NewBytes);
              }      
              //开辟成功的,就更新heapSize(记录总共向系统申请了多少内存),,更新_endFree
              _heapSize += NewBytes;
              _endFree = _startFree + NewBytes;
              return _chunkAlloc(size, nobjs);             //内存池开辟好的话,就再调一次chunk分配内存
       }
}
 
typedef _DefaultAllocTemplate<0,0>  default_alloc;

3. Other problems with the space configurator

1. All functions and variables in the space configurator are static, so they will be released after the program ends. The secondary space configurator does not return the requested memory to the operating system, and knowledge hangs them in the free linked list, and only returns it to the operating system when the program ends.
2. Since the memory is not returned, there will be two extreme cases.
2.1 Continuously open up small blocks of memory, and finally the entire heap hangs in the free linked list and is not used, and then trying to open up a large memory fails.
2.2 Continuously open up char, and finally hang the entire heap behind the first node of the free linked list, and then open up a 6-byte memory, which fails.
3. The secondary space configurator causes the problem of memory fragmentation. In extreme cases, char has been applied all the time, wasting 7/8 of the space.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324457559&siteId=291194637