Detailed explanation of the space configurator in STL (SGI version)

Space Configurator

1. What is Space Configurator

Efficient management of space (application and recycling of space) for each container

2. Why the space configurator is needed

Various containers -----> can store elements ----> the bottom needs space

new application space
  1. operator new ---->malloc
  2. Call the constructor ------ complete the construction of the object
Dynamic memory management summary

In the previous container, new is used every time the space is opened, but there are some disadvantages in using new

  • Space application and release need to be managed by users themselves, which is easy to cause memory leak
  • Frequently applying for small memory blocks from the system can easily cause memory fragmentation. For example: nodes
  • Frequent applications for small blocks of memory from the system affect the efficiency of the program
  • Apply directly with malloc and new, there is extra space wasted before each block of space (malloc will add new things before and after the applied space)
  • How to deal with space application failure
  • The code structure is confusing and the code reuse rate is not high
  • Thread safety issues are not considered

Efficient memory management

3. How to implement SGI-STL space configurator

Small memory
  1. Greater than 128 bytes -----> large block of memory
  2. Less than or equal to 128 bytes -----> small memory
  • The first-level space configurator handles large blocks of memory,
  • The secondary space configurator handles small blocks of memory.

Level 1 Space Configurator

malloc+free----->set_new_handle

_malloc_alloc_template:

Application space
void * allocate(size_t n)
{
	// 申请空间成功,直接返回,失败交由oom_malloc处理
	void * result =malloc(n);
	if(nullptr == result)
		result = oom_malloc(n);
	return result;
}
Free up space
static void deallocate(void *p, size_t /* n */)
{ 
	free(p);
}
Handling after malloc failure oom_malloc
  1. Accept function pointer (call set_new_handle)
  2. Verify that the function pointer is empty
    • Yes: throw exceptions directly
  3. Call the function corresponding to the function pointer
  4. Call malloc to continue to apply for space
    • Successful application: return directly
    • Application failed: cycle continues
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
	void (* my_malloc_handler)();
	void *result;
	for (;;)
	{
		// 检测用户是否设置空间不足应对措施,如果没有设置,抛异常,模式new的方式
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler)
		{
			__THROW_BAD_ALLOC;
		}
		// 如果设置,执行用户提供的空间不足应对措施
		(*my_malloc_handler)();
		// 继续申请空间,可能就会申请成功
		result = malloc(n);
		if (result)
			return(result);
	}
}
set_new_handle

The return value type and parameter type are both void * () function pointers

// 该函数的参数为函数指针,返回值类型也为函数指针
// void (* set_malloc_handler( void (*f)() ) )()
static void (* set_malloc_handler(void (*f)()))()
{
	void (* old)() = __malloc_alloc_oom_handler;
	__malloc_alloc_oom_handler = f;
	return(old);
}

Secondary space configurator

Defects caused by frequently applying for small memory blocks from the system
SGI-STL uses the technology of memory pool to increase the speed of applying for space and reduce the waste of extra space, and adopts the method of hash bucket to improve the speed and efficient management of users to obtain space .

Memory pool

The memory pool is: first apply for a larger memory block and make a spare. When memory is needed, go directly to the memory pool. When there is not enough space in the pool, then go to the memory. When the user does not use it, directly return it. Just go back to the memory pool. Avoid the problems of low efficiency, memory fragmentation and extra waste caused by frequently applying for small blocks of memory from the system

char* _start,*finish

Insert picture description here

Application space
  1. Find the right block in the memory block that has been returned now
  2. Find ----> Need to split ----> No need ----> Direct allocation
    Need -----> Then split the memory block
  3. Not found -----> Go to the memory pool to apply
Defects in the application space
  1. Linked list search for suitable memory block, need to traverse, low efficiency
  2. Split
Pay attention to the problem
  1. When the user needs space, can it be directly intercepted from the large space in the memory pool? why?
    Answer: It is preferred to choose from the linked list, first from the large block, if the user needs a large block of space, it may not be given
  2. Can the space returned to the user be spliced ​​directly in front of a large block of memory?
    Answer: No
  3. How to manage the space returned by users?
    Answer: Connect with linked list
  4. What are the consequences of continuous cutting?
    Answer: Memory fragmentation

Design of the secondary space configurator

The secondary space configurator in SGI-STL uses the memory pool technology, but does not use the linked list to manage the space that the user has returned (because the user is inefficient in finding suitable small blocks of memory when applying for space), and It is more efficient to use the hash bucket for management . Does it require 128 buckets of space to manage the memory blocks that the user has returned? The answer is no, because the space applied by users is basically an integer multiple of 4, and other sizes of space are rarely used . Therefore: SGI-STL aligns the memory block requested by the user up to an integer multiple of 8

32-bit systems use multiples of 4
64-bit systems use multiples of 8

There must be a pointer in each linked list, a 32-bit pointer is 4 bytes, and a 64-bit pointer is 8 bytes
Insert picture description here

template <int inst>
class __default_alloc_template
{
private:
	enum { __ALIGN = 8 }; // 如果用户所需内存不是8的整数倍,向上对齐到8的整数倍
	enum { __MAX_BYTES = 128 }; // 大小内存块的分界线
	enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; // 采用哈希桶保存小块内存时所需桶的个数
	// 如果用户所需内存块不是8的整数倍,向上对齐到8的整数倍
	static size_t ROUND_UP(size_t bytes)
	{
		return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
	}
private:
	// 用联合体来维护链表结构----同学们可以思考下此处为什么没有使用结构体
	union obj
	{
		union obj * free_list_link;
		char client_data[1]; /* The client sees this. */
	};
private:
	static obj * free_list[__NFREELISTS];
	// 哈希函数,根据用户提供字节数找到对应的桶号
	static size_t FREELIST_INDEX(size_t bytes)
	{
		return (((bytes)+__ALIGN - 1) / __ALIGN - 1);
	}
	// start_free与end_free用来标记内存池中大块内存的起始与末尾位置
	static char *start_free;
	static char *end_free;
	// 用来记录该空间配置器已经想系统索要了多少的内存块
	static size_t heap_size;
	// ...跨平台操作,封装锁,申请空间方式等
};

Management space of the secondary space configurator

void allocate (size_t n)
{
if (n> 128)
; // Call the first-level space configurator
1. Use n to calculate the bucket number
2. Check whether there is a node (memory block) in the bucket
: Use the head deletion method to The first memory block returns
no: return refill(Round_up(n));
}

void refill (size_t / is already an integer multiple of 8 /)
{
size_t nobjs = 20;
char * chunk = chunk_alloc (n, nobjs);
if (nobjs == 1) // as long as a
return chunk;
// 1 <nobjs <= 20
to the first memory storage, and finally to return to the external user
will nobjs-1 remaining attached to the block of memory corresponding bucket
}

void * chunk_alloc(size_t size,size_t& nobjs//20)
{
size_t totalBytes = nobjs*size;
size_t leftBytes = finish-start;

void * res = null;
if (leftBytes> = totalBytes) // Memory pool space is enough to provide 20
{res = start; start + = totalBytes; return res;}
else if (leftBytes> = size) // Not enough 20 to provide at least 1 block
{nobjs = leftBytes / size; res = start; start + = nobjs size; return res;} // How many blocks can actually be provided
// There is not enough space in the memory pool, and even one piece cannot be provided
{
// 1. The memory pool The remaining memory
in- > hooked into the corresponding bucket // total_get = 2
total_bytes + RoundUP (heap_size >> 4);
}
}

  1. Application space
    Insert picture description here
// 函数功能:向空间配置器索要空间
// 参数n: 用户所需空间字节数
// 返回值:返回空间的首地址
static void * allocate(size_t n)
{
	obj * __VOLATILE * my_free_list;
	obj * __RESTRICT result;
	// 检测用户所需空间释放超过128(即是否为小块内存)
	if (n > (size_t)__MAX_BYTES)
	{
		// 不是小块内存交由一级空间配置器处理
		return (malloc_alloc::allocate(n));
	}
	// 根据用户所需字节找到对应的桶号
	my_free_list = free_list + FREELIST_INDEX(n);
	result = *my_free_list;
	// 如果该桶中没有内存块时,向该桶中补充空间
	if (result == 0)
	{
		// 将n向上对齐到8的整数被,保证向桶中补充内存块时,内存块一定是8的整数倍
		void *r = refill(ROUND_UP(n));
		return r;
	}
	// 维护桶中剩余内存块的链式关系
	*my_free_list = result->free_list_link;
	return (result);
};
  1. Reclaimed space
    Insert picture description here
    does not free up space in the secondary space configurator
// 函数功能:用户将空间归还给空间配置器
// 参数:p空间首地址 n空间总大小
static void deallocate(void *p, size_t n)
{
	obj *q = (obj *)p;
	obj ** my_free_list;
	// 如果空间不是小块内存,交给一级空间配置器回收
	if (n > (size_t)__MAX_BYTES)	//超过128,按照一级空间配置进行释放
	{
		malloc_alloc::deallocate(p, n);
		return;
	}
	//没到128
	// 找到对应的哈希桶,将内存挂在对应哈希桶中
	my_free_list = free_list + FREELIST_INDEX(n);
	q->free_list_link = *my_free_list;
	*my_free_list = q;
}
  1. Add space to memory

Insert picture description here

template <int inst>
char* __default_alloc_template<inst>::chunk_alloc(size_t size, int& nobjs)
{
	// 计算nobjs个size字节内存块的总大小以及内存池中剩余空间总大小
	char * result;
	size_t total_bytes = size * nobjs;
	size_t bytes_left = end_free - start_free;
	// 如果内存池可以提供total_bytes字节,返回
	if (bytes_left >= total_bytes)
	{
		result = start_free;
		start_free += total_bytes;
		return(result);
	}
	else if (bytes_left >= size)
	{
		// nobjs块无法提供,但是至少可以提供1块size字节内存块,提供后返回
		nobjs = bytes_left / size;
		total_bytes = size * nobjs;
		result = start_free;
		start_free += total_bytes;
		return(result);
	}
	else
	{
		// 内存池空间不足,连一块小块村内都不能提供
		// 向系统堆求助,往内存池中补充空间
		// 计算向内存中补充空间大小:本次空间总大小两倍 + 向系统申请总大小/16
		size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
		// 如果内存池有剩余空间(该空间一定是8的整数倍),将该空间挂到对应哈希桶中
		if (bytes_left > 0)
		{
			// 找对用哈希桶,将剩余空间挂在其上
			obj ** my_free_list = free_list + FREELIST_INDEX(bytes_left);
			((obj *)start_free)->free_list_link = *my_free_list;
			*my_ree_list = (obj *)start_free;
		}
		// 通过系统堆向内存池补充空间,如果补充成功,递归继续分配
		start_free = (char *)malloc(bytes_to_get);
		if (0 == start_free)
		{
			// 通过系统堆补充空间失败,在哈希桶中找是否有没有使用的较大的内存块
			int i;
			obj ** my_free_list, *p;
			for (i = size; i <= __MAX_BYTES; i += __ALIGN)
			{
				my_free_list = free_list + FREELIST_INDEX(i);
				p = *my_free_list;
				// 如果有,将该内存块补充进内存池,递归继续分配
				if (0 != p)
				{
					*my_free_list = p->free_list_link;
					start_free = (char *)p;
					end_free = start_free + i;
					return(chunk_alloc(size, nobjs));
				}
			}
			// 山穷水尽,只能向一级空间配置器求助
			// 注意:此处一定要将end_free置空,因为一级空间配置器一旦抛异常就会出问题
			end_free = 0;//end_free作用是标记内存池空间的末尾
			start_free = (char *)malloc_alloc::allocate(bytes_to_get);
		}
		// 通过系统堆向内存池补充空间成功,更新信息并继续分配
		heap_size += bytes_to_get;
		end_free = start_free + bytes_to_get;
		return(chunk_alloc(size, nobjs));
	}
}

Space Configurator's default selection

SGI-STL uses the first-level or second-level space configurator by default, controlled by the USE_MALLOC macro:

#ifdef __USE_MALLOC
	typedef malloc_alloc alloc;
	typedef malloc_alloc single_client_alloc;
#else
	// 二级空间配置器定义
#endif

This macro is not defined in SGI_STL, so: by default SGI_STL uses the secondary space configurator

Repackaging of the space configurator

In C ++, the space required by the user may be of any type, with a single object space and continuous space. It is not very friendly to let the user calculate the total size of the space required each time, so SGI-STL re-encapsulates the space configurator layer:

template<class T, class Alloc>
class simple_alloc
{
public:
	// 申请n个T类型对象大小的空间
	static T *allocate(size_t n)
	{
		return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof (T));
	}
	// 申请一个T类型对象大小的空间
	static T *allocate(void)
	{
		return (T*)Alloc::allocate(sizeof (T));
	}
	// 释放n个T类型对象大小的空间
	static void deallocate(T *p, size_t n)
	{
		if (0 != n)
			Alloc::deallocate(p, n * sizeof (T));
	}
	// 释放一个T类型对象大小的空间
	static void deallocate(T *p)
	{
		Alloc::deallocate(p, sizeof (T));
	}
};

Object construction and release

For efficiency reasons, SGI-STL decided to separate the two processes of space application release and object construction and destruction, because the construction of some objects does not need to call the destructor, and the destruction does not need to call the destructor to separate the process Open can improve the performance of the program:

// 归还空间时,先先调用该函数将对象中资源清理掉
template <class T>
inline void destroy(T* pointer)
{
	pointer->~T();
}
// 空间申请好后调用该函数:利用placement-new完成对象的构造
template <class T1, class T2>
inline void construct(T1* p, const T2& value)
{
	new (p)T1(value);
}
Published 253 original articles · praised 41 · 40,000+ views

Guess you like

Origin blog.csdn.net/liuyuchen282828/article/details/103982896