Understanding STL-Space Configurator

Preface

        The space allocator, as the name suggests, is for efficient management of space (application and recycling of space) for each container, and works silently. Although you may not need it when using STL on a regular basis, from the perspective of learning and research, learning its implementation principles is of great help to us.

Table of contents

1. Why you need a space configurator

2.SGI-STL space configurator implementation principle

3. Implementation principle of first-level space configurator

        3.1 First-level space configurator

        3.2 Secondary space configurator

                3.2.1 Memory pool

                3.2.2 Design of secondary space configurator in SGI-STL

        3.3 Space application of SGI-STL secondary space configurator

                3.3.1 Early preparation

                3.3.2 Apply for space

                        3.3.2.1 Filling memory blocks

                        3.3.2.2 Ask for space in the memory pool

                3.3.3 Space recycling

        3.4 Default selection of space configurator

        3.5 Re-encapsulation of spatial configurator

        3.6 Construction and release of objects

        


1. Why you need a space configurator

        Although the space applied for by new can be used, it has the following shortcomings:

        1. Space application and release need to be managed by the user themselves, which can easily cause memory leaks. 

        2. Frequently requesting small memory blocks from the system can easily cause memory fragmentation.

        3. Frequently applies for small blocks of memory from the system, affecting program operation efficiency.

        4. Directly using malloc and new to apply for each block of space will waste extra space.

        5. How to deal with failed application

        6. The code structure is relatively confusing and the code reuse rate is not high.

        7. Failure to consider thread safety issues

        Because of these problems, an efficient memory management mechanism is needed. 

2.SGI-STL space configurator implementation principle

        Frequently applying for small blocks of memory from the system will cause many problems. So what kind of memory is considered a small block of memory?

        SGI-STL uses 128 as the dividing line between large memory and small memory, and divides the space allocator into a two-level structure. The first-level space allocator handles large blocks of memory, and the second-level space allocator handles small blocks of memory. 

3. Implementation principle of first-level space configurator

        3.1 First-level space configurator

        The principle of the first-level space configurator is very simple. It directly encapsulates malloc and free, and adds the idea of ​​set_new_handle in C++.

namespace qyy
{
	
	template <int inst>
	class __malloc_alloc_template
	{
	private:
		static void* oom_malloc(size_t);
	public:
		//对malloc进行封装
		static void* allocate(size_t n)
		{
			//申请空间成功直接返回,失败了交给oom_malloc处理
			void* result = malloc(n);
			if (result == 0)
			{
				result = oom_malloc(n);
			}
			return result;
		}
		//对free进行封装
		static void* deallocate(void* p, size_t)
		{
			free(p);
		}
		//模拟实现set_malloc_handler
		//该函数参数为函数指针,返回值也是函数指针
		//void(*set_malloc_handler(void*(f*)( )  )) ()
		static void (*set_malloc_hander(void (*f)()))()
		{
			void (*old)() = __malloc_alloc_oom_handler;
			__malloc_alloc_oom_handler = f;
			return (old);
		}
	};
	//malloc空间申请失败时调用该函数
	template<int inst>
	void* __malloc_alloc_template<inst>::oom_malloc(size_t)
	{
		void(*my_malloc_handler)();
		void* result;
		for (;;)
		{
			//检测用户是否设置空间不足应对措施,如果没有设置,抛异常模拟new的方式
			my_malloc_handler = __malloc_alloc_handler;
			if (0 == my_malloc_handler)
			{
				__THROW_BAD_ALLOC;
			}
			//如果设置了,执行用户提供空间不足应对措施
			(*my_malloc_handler)();
			//继续申请空间可能就会成功
			result = malloc(n);
			if (result)
				return (result);
		}
	}
	typedef __malloc_alloc_template<0> malloc_alloc;

}

        3.2 Secondary space configurator

        The secondary space allocator is specifically responsible for processing small blocks of space less than 128 bytes. How can we improve the way of applying for and releasing small blocks of memory? SGI-STL uses memory pool technology to improve the speed of space application and reduce the waste of additional space, and uses hash buckets to improve the speed of user acquisition of space and efficient management. 

                3.2.1 Memory pool

        The memory pool is: first apply for a relatively large memory as a backup. When the memory is needed, just go to the memory pool to get the memory. When the space in the memory pool is not enough, then get it from the memory. When the user does not use it, Just return it to the memory pool. This avoids the problems of low efficiency, memory fragmentation and extra waste caused by frequently applying for small blocks of memory from the system. 

                3.2.2 Design of secondary space configurator in SGI-STL

        The secondary space allocator in SGI-STL uses memory pool technology, but does not use a linked list to manage the space that has been returned by the user (because when users apply for space, it is inefficient to find a suitable small block of memory). And adopt the method of bucket collection for management. So are there 128 buckets to manage the returned space? The answer is no, because users basically apply for space that is an integer multiple of 4, and other sizes of space are rarely used. Therefore: SGI-STL adjusts the memory block applied by the user upward to 8. Integer multiple.  

        3.3 Space application of SGI-STL secondary space configurator

                3.3.1 Early preparation

namespace qyy
{
	template<int inst>
	class __default_alloc_template
	{
	private:
		enum {__ALIGN = 8};//如果用户需要的内存不是8的整数倍,向上对其到8的整数倍
		enum {__MAX_BYTES = 128};//大小块内存的分界线
		enum { __NFREELISTS = __MAX_BYTES / __ALIGN };//采用哈希桶保存小块内存时所需要的内存个数
		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];
		};
	private:
		static obj* free_list[__NFREELISTS];
		//哈希函数根据用户提供的字节数找到对应的桶号
		static size_t FREELIST_INDEX(size_t bytes)
		{
			return (((bytes)+__ALIGN - 1) / ALING - 1);
		}
		//start_free 和end_free用来标记内存池中大块内存的起始和末尾位置
		static char* start_free;
		static char* end_free;

		//用来记录该空间配置器已经向系统索要了多少的块内存
		static size_t heap_size;
		///...
	};
}

                3.3.2 Apply for space


		//函数功能:向空间配置器索要空间
		//参数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_lsit_link;
			return (result);
		}

                        3.3.2.1 Filling memory blocks

		//函数功能:向哈系桶里面补充空间
		//参数n小块内存
		//首个小块内存的首地址
		template<int inst>
		void* __default_alloc_template<inst>::refill(size_t n)
		{
			//一次性向内存池索要20个n字节的空间
			int nobjs = 20;
			char* chunk = chunk_alloc(n, nobjs);

			obj** my_free_list;
			obj* result;
			obj* current_obj, * next_obj;
			int i;
			//如果只有一块直接返回给用户
			if (1 == nobjs)
				return (chunk);
			//找到对应的桶号
			my_free_list = free_list + FREELIST_INDEX(n);

			//将第一块返回给用户,去其他的挂到哈系桶中

			result = (obj*)chunk;
			*my_free_list = next_obj = (obj*)(chunk + n);
			for (i = 1;;i++)
			{
				current_obj = next_obj;
				next_obj = (obj*)((char*)next_obj + n);
				if (nobjs - 1 == i)
				{
					current_obj->free_list_link = 0;
					break;
				}
				else
				{
					current_obj->free_list_link = next_obj;
				}

			}
			return (result);
		}

                        3.3.2.2 Ask for space in the memory pool

 

	    template<int inst>
		char* __default_alloc_tempalte<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 >= tatal_bytes)
			{
				result = start_free;
				start_free += tatal_bytes;
				return (result);
			}
			else if (bytes_left >= size)
			{
				//nobjs块无法提供,但是至少可以提供1块size字节的内存块,提供后返回
				nobjs = bytes_left / size;
				tatal_byte = size * nobjs;
				result = start_free;
				start_free += tatal_bytes;
				return (result);
			}
			else
			{
				//内存池空间不够连一小块内存都没有。
				//向系统堆求助,往内存池中补充空间
				//计算向内存中补充空间的大小:本次空间总大小的两倍 + 向系统申请总大小/16
				size_t bytes_to_get = 2 * tatal_byte + 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;
					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));
			}
		}

                3.3.3 Space recycling

		//函数功能:用户将空间归还给空间配置器
		//参数:p空间首地址 n空间总大小
		static void deallocate(void* p, size_t n)
		{
			obj* q = (obj*)p;
			obj** my_free_list;
			if (n > (size_t)__MAX_BYTES)
			{
				malloc_alloc::deallocate(p, n);
				return;
			}
			//找到对应的哈系桶,将内存挂到哈系桶中
			my_free_list=free_list+ FREELIST_INDEX(n);
			q->free_list_link = *my_free_list;
			*my_free_list = q;
		}

        3.4 Default selection of space configurator

        SIG_STL uses the first-level space configurator or the second-level space configurator by default, which is controlled by the macro switch USE_MALLOC macro.

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

        3.5 Re-encapsulation of spatial configurator

        In C++, the space required by the user may be of any type, including space for a single object and continuous space. It is not very friendly for the user to calculate the total size of the space each time. Therefore, SGI_STL re-encapsulates the space configurator. layer.

template<class T,class ALLOC>
	class simlpe_alloc
	{
	public:
		//申请n个T类型对象大小的空间
		static T* allocate(size_t n)
		{
			return 0 == n ? 0 : (T*)ALLOC::allocte(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));
			}
		}
		static void deallocate(T* p)
		{
			ALLOC::deallocate(p, sizeof(T));
		}
	

 

        3.6 Construction and release of objects

        Because of the need for efficiency, SGI_STL decided to separate the two processes of space application and release and object construction and destruction. Because some objects do not need to call the constructor when constructing, and the destructor does not need to be called when destroying. Separating this process can improve the efficiency. Program performance.

	//归还空间时,先调用析构函数进行资源的清理

	template <class T>
	inline void destory(T* pointer)
	{
		pointer->~T();
	}
	//空间申请好后,调用该函数:利用placement-new完成对对象的构造
	template<class T1,class T2>
	inline void construct(T1* p, const T2& value)
	{
		new(p)T1(value);
	}

         Note that when releasing an object, you need to determine whether the destructor needs to be called (type extraction) based on the type of the object.

        The type of an object can be extracted using an iterator.

        These contents are more difficult to understand. (for beginners) 

 

        

Guess you like

Origin blog.csdn.net/m0_68641696/article/details/132522861