STL-Space Configurator III (Second Level Configurator)

1 Introduction

The design idea of ​​the second level configurator is:

  1. If the configuration block exceeds 128 bytes, it will be handed over to the first-level configurator for processing;
  2. If the configuration block is less than 128 bytes, the memory pool management (memory pool) is adopted.
  3. Each time a large block of memory is configured, the corresponding free-list is maintained. Next time, if there is a memory requirement of the same size, it will be directly
    dialed out from the free-list (if there is no memory, continue to configure the memory, the details will be described later. ), if the client releases the small block, the configurator will reclaim it in the free-list.

Insert picture description here

The concept of the memory pool: the memory pool is to apply for the allocation of a certain number of memory blocks of equal size (under normal circumstances) for spare use before the memory is actually used. When there is a new memory demand, a part of the memory block is divided from the memory pool, and if the memory block is not enough, then continue to apply for new memory. In this way, the memory pool allows memory blocks to be planned in a constant time during runtime, and memory fragmentation is avoided as much as possible, so that the efficiency of memory allocation is improved.

Part of the source code of the second level configurator

free_list node structure

/* part of stl source code v3.3 */
 
enum {
    
     _ALIGN = 8 };                //小型区块的上调边界
enum {
    
     _MAX_BYTES = 128 };          //小型区块的上限
enum {
    
     _NFREELISTS = 16 }; // _MAX_BYTES/_ALIGN   //free-list 编号数
 
//配置内存后,维护对应内存块的空闲链表节点结构
union _Obj {
    
    
	union _Obj* _M_free_list_link;   //空闲链表
	char _M_client_data[1];    /* The client sees this. 用户使用的*/
};

This free-list has two functions: one is to point to the next piece of blank memory (when it exists in the free-list), and the other is a piece of memory for users to use (not in the free-list).

Auxiliary functions _S_round_up and _S_freelist_index

/*将 __bytes 上调至最邻近的 8 的倍数*/
static size_t
_S_round_up(size_t __bytes)
{
    
    
	return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));
}
 
/*返回 __bytes 大小的小额区块位于 free-list 中的编号*/
static  size_t _S_freelist_index(size_t __bytes) {
    
    
	return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);
}

**allocate()** allocate space

static void* allocate(size_t __n)   //分配大小为 __n 的区块
{
    
    
	void* __ret = 0;
 
	if (__n > (size_t)_MAX_BYTES) {
    
    
		__ret = malloc_alloc::allocate(__n);    //大于128 bytes 就调用第一级配置器
	}
	else {
    
     //__n 大小区块对应的位置:free-lists 首地址 + __n 位于free-lists 中的编号
		_Obj* __STL_VOLATILE* __my_free_list    //这里是二级指针,便于调整 free-lists 
			= _S_free_list + _S_freelist_index(__n);  		
 
#     ifndef _NOTHREADS
		_Lock __lock_instance;
#     endif
 
		_Obj* __RESTRICT __result = *__my_free_list; //将对应位置的区块拨出(第一个)
		if (__result == 0)                       //如果 free-lists 中没有对应大小的区块
			__ret = _S_refill(_S_round_up(__n)); //调用 _S_refill()
		else {
    
    
			*__my_free_list = __result->_M_free_list_link;//这个结构有点类似链式哈希表结构,这里是指向下一块空闲内存块  
			//二级指针调整 free-lists,拨出去的区块就不属于该链表了
			__ret = __result;                               
		}
	}
	return __ret;
};

Each time, the available memory block is taken out from the head of the corresponding free_list. Then adjust the free_list so that the next node of the memory allocated in the previous step becomes the head node. If there is no block of the corresponding size in the free-list, go to call _S_refill().
Insert picture description here

_S_refill()

template <bool __threads, int __inst>
void*           //重新填充__n大小的区块进 free-list
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)  
{
    
    
	int __nobjs = 20;      //缺省取得 20 个新区块
	char* __chunk = _S_chunk_alloc(__n, __nobjs);  //调用_S_chunk_alloc()
	_Obj* __STL_VOLATILE* __my_free_list;
	_Obj* __result;
	_Obj* __current_obj;
	_Obj* __next_obj;
	int __i;
 
	/*如果只获得一个新区块,直接划给用户,free-list 仍然无新节点*/
	if (1 == __nobjs) return(__chunk);  
	__my_free_list = _S_free_list + _S_freelist_index(__n);
 
	__result = (_Obj*)__chunk;   //这一块返回给客端(分配出来的第一块)
	*__my_free_list = __next_obj = (_Obj*)(__chunk + __n); 
	/*接下来的区块(拨出去了__n大小给用户)填补进 free-list*/
	for (__i = 1;; __i++) {
    
    
		__current_obj = __next_obj; 
		__next_obj = (_Obj*)((char*)__next_obj + __n); 
		/*将分配的连续大块"分割"成__n bytes大小的区块*/
		if (__nobjs - 1 == __i) {
    
      //如果新区块填补完毕
			__current_obj->_M_free_list_link = 0;  //free-list 最后位置指向0
			break;
		}
		else {
    
    //把_M_free_list_link当做链表的 next 指针理解
			__current_obj->_M_free_list_link = __next_obj; //将各节点串联起来填进空闲链表
		}
	}
	return(__result);
}

In this way, the remaining "large block" of memory returned to the client will be "divided" into blocks of the specified size and filled into the free-list. The new chunk is taken from the memory pool and is completed by _S_chunk_alloc().
_S_chunk_alloc()

static char* _S_start_free;   //内存池起始位置
static char* _S_end_free;     //内存池末端位置
static size_t _S_heap_size;   //堆空间容量
 
template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_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;  //内存池中还剩余多少可用内存
 
	if (__bytes_left >= __total_bytes) {
    
        //剩余可用量大于需求量,直接划分出去
		__result = _S_start_free;           //内存池的首地址
		_S_start_free += __total_bytes;     //调整内存池位置
		return(__result);  
	}
	//内存池剩余空间不能完全满足需求,但至少可供应一个及以上的区块
	else if (__bytes_left >= __size) {
    
          
		__nobjs = (int)(__bytes_left / __size);  //调整划分个数,划分出去最大量
		__total_bytes = __size * __nobjs;        //同上
		__result = _S_start_free;
		_S_start_free += __total_bytes;
		return(__result);
	}
	else {
    
        //内存池剩余空间连一个区块都无法提供
		size_t __bytes_to_get =//配置大小为总需求量的两倍再加上一个随配置次数逐渐增加的附加量
			2 * __total_bytes + _S_round_up(_S_heap_size >> 4); /
	
		if (__bytes_left > 0) {
    
       //充分利用内存池中的剩余空间
			_Obj* __STL_VOLATILE* __my_free_list =   //剩余空间寻找适当的free-list
				_S_free_list + _S_freelist_index(__bytes_left);   
         //调整 free-list,将内存池中残余空间编入 free-list 对应位置中
			((_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);  //配置heap空间,用来补充内存池
		if (0 == _S_start_free) {
    
             //system heap 空间不足,分配失败
			size_t __i;
			_Obj* __STL_VOLATILE* __my_free_list;
			_Obj* __p;
 
			for (__i = __size;                 //起始大小为需求区块大小
				__i <= (size_t)_MAX_BYTES;      
				__i += (size_t)_ALIGN) {
    
           //以 8 为步长搜寻整个 free-list
				__my_free_list = _S_free_list + _S_freelist_index(__i);  //找到 __i大小区块在free-list 中的位置
				__p = *__my_free_list;
				if (0 != __p) {
    
                   //如果 free-list 中该区块未使用
					*__my_free_list = __p->_M_free_list_link;   //调整 free-list,释放第一位置块
					_S_start_free = (char*)__p;              //编入内存池
					_S_end_free = _S_start_free + __i;       
					return(_S_chunk_alloc(__size, __nobjs));    //递归调用
        /*该for循环的作用就是从 free-list 中划出大于需求区块(单个)的未用空间区块到内存池,然后再从内存池中取出。
		由于从大于__size 的区块开始搜寻,所以如果 free-list 中搜寻到,那么只需动用该搜寻区块的第一位置区块即可,
		最后取出的空间也可能是单个区块,也可能是多个区块(取决于 free-list 未用区块的最小区块(大于__size)的大小)*/
				}
			}
			_S_end_free = 0;	//表示到处无内存可用,for循环中free-list没有搜寻到适当的未用空间
			_S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); //调用第一级配置器
			/*要么内存不足的问题获得改善,要么抛出 bad_alloc 异常*/
		}
		_S_heap_size += __bytes_to_get;   //调用第一级配置器后,内存不足问题获得改善,调整堆空间
		_S_end_free = _S_start_free + __bytes_to_get;    //编入内存池
		return(_S_chunk_alloc(__size, __nobjs));   //重新调用 _S_chunk_alloc()
	}
}

The concrete realization idea of ​​this function is:

  1. If the remaining space in the memory pool fully meets the demand for 20 blocks, the space of the corresponding size is directly taken out;
  2. If the remaining space in the memory pool cannot fully meet the demand for 20 blocks, but one or more blocks can be provided, then take out the space that can meet the maximum number of required blocks;
  3. If the remaining space in the memory pool cannot meet the size of a required block, first judge whether there is any residual memory space in the pool, and if there is any residual memory space, it will be recycled, and it will be assigned
    to the appropriate position in the free-list ; then apply to the system heap Space to supplement the memory pool. If the heap space is sufficient, the space allocation is successful; if the heap
    space is insufficient, the malloc() call fails. Then search for the appropriate free-list (appropriate refers to the free-list with "there are no blocks yet, and the block is larger"
    ), that is, search for a block with a free-list greater than or equal to the required block, and program it into the memory pool , And then recursively call the
    _S_chunk_alloc() function to take space from the memory pool to repeat the above process. If unfortunately, there is no suitable memory space available in the free-list, then the first-level configurator is called and the out-of-memory mechanism is used to try to solve the problem of insufficient memory.
    As a result, either the insufficient memory situation is improved or the problem is thrown. A bad_alloc exception occurred.

reallocate() redistribution

 static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz)
 {
    
    
  	void* __result;
    size_t __copy_sz;
    if (__old_sz > (size_t) _MAX_BYTES && __new_sz > (size_t) _MAX_BYTES) {
    
    
    	//如果太大,直接调一级的realloc
        return(realloc(__p, __new_sz));
    }
    if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);
    //上调至最近的倍数发现,一样大,则直接返回
    __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);
 }

deallocate() to reclaim space

/*空间释放后被编入 free-list*/
static void deallocate(void* __p, size_t __n)
{
    
    
	if (__n > (size_t)_MAX_BYTES)  //大于 128 就调用第一级配置器
		malloc_alloc::deallocate(__p, __n);
	else {
    
    
		_Obj* __STL_VOLATILE*  __my_free_list  //定位到对应的 free-list
			= _S_free_list + _S_freelist_index(__n);
		_Obj* __q = (_Obj*)__p;       
 
#       ifndef _NOTHREADS
		_Lock __lock_instance;
#       endif 
 
		__q->_M_free_list_link = *__my_free_list;  //调整 free-list 回收区块
		*__my_free_list = __q;        //回收的区块是挂接到free-list 对应区块的第一位置,而不是添加到尾端
	}
}

Three STL source code

If you are interested, you can go to see the STL source code

Guess you like

Origin blog.csdn.net/GreedySnaker/article/details/114094540