Space adapter (SGI-STL)

Understand

It is a mechanism to manage space, allocate and recycle space
Importance: the operation objects of the whole STL are stored in the container, and the container needs to configure space to place data. Space configurator realizes efficient space management for each container

Why not just use new/delete?

  1. Users manage their own space, easy to leak memory
  2. Frequent application of small memory space causes memory fragmentation
  3. In the process of calling function frequently, stack pressing is needed, which is inefficient
  4. Additional space waste (space size applied + space managed)
  5. Possible application failure

Why not say that allocator is a memory configurator?
Because space is not necessarily memory, it can be disk / other secondary storage media

Size of memory block under SGI-STL?
Large memory block with more than 128 bytes
Small memory block with less than 128 bytes -- secondary space configurator processing

Realization

The SGI STL configurator is different from the standard specification, with the name alloc instead of allocator, and does not accept any parameters:

//Standard writing:
vector<int,std::allocator<int>> v;

//Using SGI Configurator
vector<int,alloc> v;

Each container of SGI STL specifies that the default configurator is alloctemplate < class T, class alloc = alloc >

SGI standard space configurator std::allocator

Inefficient, not recommended
SGI also defines a configurator named allocator that meets some criteria.
Only:: operator new,:: operator delete are simply packed, and efficiency enhancement is not considered

//std::allocator, in delalloc. H, just for backward compatibility
	template <class T>
	inline T* _allocate(ptrdiff_t size, T*)
	{
		set_new_handler(0);
		T* tmp = (T*)(::operator new((size_t)(size*sizeof(T))));
		if (tmp == 0)
		{
			cout << "out of memory" << endl;
			exit(1);
		}
		return tmp;
	}
	template <class T>
	class allocator
	{
	public:
		typedef T val_type;
		typedef T* pointer;
		typedef const T* const_pointer;
		typedef T& reference;
		typedef const T& const_reference;
		typedef size_t size_type;
		typedef ptrdiff_t difference_type;

		//The configuration space is enough to store n objects. The second parameter may be used to increase the culture. Only prompt can be ignored
		pointer allocate(size_type n)
		{
			return ::allocate((difference_type)n, (pointer)0);
		}
		//Return previously configured space
		void deallocate(pointer p)
		{
			::deallocate(p);
		}
		//Return the address of the object
		pointer address(reference a)
		{
			return (pointer)&a;
		}
		//Return const object address
		const_pointer const_address(const_reference a)
		{
			return (const_pointer)&a;
		}
		size_type init_page_size()
		{
			return max(sizr_type(1),size_type(4096/sizeof(T));
		}
		//Returns the maximum value that can be successfully configured
		size_type max_size() const
		{
			return size_type(UINT_MAX / sizeof(T));
		}
	};
	class allocator<void>
	{
	public:
		typedef void* pointer;
	}
SGI special space configurator std::alloc

For the following code:

class Foo{...};
Foo* f=new Foo;
delete f;

new:

  • Call:: operator new to configure memory
  • Call Foo::Foo() to construct the object content

delete:

  • Call Foo::~Foo() to deconstruct the object
  • Call:: operator delete to free memory

STL allocator separates these two phases. The memory configuration is in the charge of alloc::allocate(), memory release is in the charge of alloc::deallocate(), object construction is in the charge of:: construct(), and object analysis is in the charge of:: destroy(). Configurator is defined in, which contains two files:

#Include < stl_alloc. H > / / configuration and release of memory space
 #Include < stl_construct. H > / / construct and analyze the object content. Define two functions, construct(),destroy()

construct()/destroy(), a basic tool for construction and Deconstruction

STL specifies that the configurator must be two member functions named construct and destroy. However, the configurator of std::alloc does not follow the rule of 1

//Part of source code
#include <new.h>  //placement new
template <class T1,class T2>
//Set the initial value to the space indicated by the pointer
inline void construct(T1* p,const T2* value)
{
	new(p) T1(value);
}
//destroy version 1, accepts a pointer and constructs the object
inline void destroy(T* pointer)
{
	pointer->~T();
}
//destroy version 2, which accepts two iterators to deconstruct all objects in the scope
//Using ﹐ type ﹐ traits < > to judge whether the destructor of the object type is irrelevant and to find the most appropriate measure
//Because calling destructors on multiple objects is inefficient
template <class ForwardIterator>
inline void destroy(ForwardIterator first,ForwardIterator last)
{
	__destroy(first,last,value_type(first));
}
//Determine whether the element value category has a triple destructor
template<class ForwardIterator,classT>
inline void __destroy(ForwardIterator first,ForwardIterator last,T*)
{
	typedef typename __type_traits<T>::has_trivial_destructor trival_destroy;
	__destroy_aux(first,last,trival_destructor());
}
//If the element value category has a non trivial destructor
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first,ForwardIterator last,__false_type)
{
	for(;first<last;++first
		__destroy(&*first);
}
//If the element value category has a triple destructor
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator first,ForwardIterator last,__true_type)
{
	...
}
//The second edition of destroy's specialization of iterator char*,wchar_t *
inline void destroy(char*,char*){}
inline void destroy(wchar_t*,wchar_t*){}

Space configuration and release std::alloc

SGI's design philosophy:

  • Request space from system heap
  • Consider multithreaded state
  • Measures to deal with insufficient memory
  • Consider memory fragmentation
#ifdef __USE_MALLOC
//...
typedef __malloc_alloc_template<0> malloc_alloc //Primary space Configurator
typedef malloc_alloc alloc//Set as primary Configurator
#else
...
typedef __malloc_default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc; //Secondary space Configurator
#endif /* !__USE_MALLOC*/
//alloc does not accept any template type parameters

Regardless of whether alloc is defined as a primary / secondary space configurator, SGI shall package the interface to make the configurator interface conform to the STL specification:

//Change the configurator's configuration unit from bytes to individual element size sizeof
template <class T,class Alloc>
class simple_alloc
{
public:
	static T *allocate(size_t n)
	{
		return 0==n?0:(T*)Alloc::allocate(n*sizeof(T));
	}
	static T *allocate(void)
	{
		return (T*)Alloc::allocate(n*sizeof(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,n*sizeof(T));
	}
}

//Use
template <class T,class Alloc=alloc>//Use alloc configurator by default
class vector
{
	protected:
	typedef simple_alloc<value_type,Alloc> data_allocator;
	void deallocate()
	{
		if(...)
			data_allocator::deallocator(start,end_of_storage-start);
	}
	...
}

Space configuration function allocate()
In the second level configurator, this function first determines the block size. The first level configurator is used for large area, and the corresponding free list is checked for cell fast. If there are available blocks in the free list, they will be taken directly. If there are no available blocks, the block size will be increased to the multiple boundary, and refill will be called to refill the space

static void* allocate(size_t n)
{
	obj* volatile *my_free_list;
	obj* result;
	if(n>(size_t)__MAX_BYTES)
	{
		return (malloc_alloc::allocate(n));//Call primary Configurator
	}
	my_free_list=free_list+FREELIST_INDEX(n);
	result=*my_free_list;
	if(result==0)
	{
		//Fill free list
		void *r=refill(ROUND_UP(n));
		return r;
	}
	*my_free_list=result->free_list_link;
	return (result);
};

Repopulate free list
The new space will be taken from the memory pool, which will be completed by the chunk_alloc(). By default, 20 new nodes will be obtained. In case of insufficient memory pool, it may be less than 20 nodes

deallocate() space release function
In the second level space configurator, first determine the block size, quickly call the first level space configurator in large area, and then find out the corresponding free list in small area to recycle the block

static void deallocate(void *p,size_t n)
{
	obj *q=(obj*)p;
	obj *volatile *my_free_list;
	if(n>(size_t)__MAX_BYTES)
	{
		malloc_alloc::deallocate(p,n);
		return;
	}
	my_free_list=free_list+FREELIST_INDEX(n);
	//recovery
	q->free_list_link=*my_free_list;
	*my_free_list=q;
}

Packaging interface and application mode of primary space configurator and secondary space Configurator

Primary space Configurator

__malloc_alloc_template

Use malloe(),free(),realloc(), and other functions to perform the actual memory configuration, release, and reconfigure operations, and implement a mechanism similar to C + + new handler (you can't directly use the C + + new handler mechanism, because it uses malloc instead of:: operator new to configure the internal storage). Since you can't use the C + + set_new_handler(), you can only simulate a set_malloc_handler())

newhandler mechanism:
You can ask the system to call a function you specify when the memory configuration requirements cannot be met. In other words, once:: operator new fails to complete the task, it will call the handler specified by the client before throwing out the STD:: bad · alloc exception state, which is called new handler

Be careful
allocate() and realloc() both call oo'u malloc() and oo'u realloc() after unsuccessful calls to malloc() and realloc(). Both of them have inner loop, calling "out of memory processing routine" constantly, expecting to get enough memory to complete the task successfully after a certain call. However, if the "out of memory processing routine" is not set by the client, the oom? Malloc() and oom? Realloc() call the? Row? Bad? Alloc, throw out badlloc exception information, or use exit(1) to abort the program

It is the client's responsibility to design "out of memory processing routine"
Setting the "out of memory processing routine" is also the client's responsibility
"Out of memory processing routines" have specific solutions

Secondary space Configurator

__default_alloc_template

There are many mechanisms in the secondary configurator to avoid memory fragmentation caused by too many small blocks. In fact, small blocks bring not only memory fragmentation, but also the extra burden of configuration. The extra burden can never be avoided, because the system relies on the extra space to manage the memory, as shown in the figure:


The smaller the block is, the larger the proportion of extra burden will be and the more wasteful it will be

Realization
If the block is large enough to exceed 128 bytes, it will be handed over to the first level configurator for processing. When the block size is less than 128 bytes, the internal storage pool is managed. This method is also called sub layer configuration: one large block of memory is configured at a time, and the corresponding free list is maintained. If there is another memory requirement of the same size, the next time, the memory is directly allocated from the list. If the client releases small blocks, they will be recycled to the linked list by the configurator
In order to facilitate management, the second level configurator of SGI will actively increase the memory demand of any small block to a multiple of 8 (eg: if the client requires 30 bytes, it will be automatically adjusted to 32 bytes), and maintain 16 feelies, with their respective management sizes of 8, 16, 24, 32.40, 48, 56, 64, 72, 80.88, 96, 104, 112, 120, 128 bytes
Nodes of free lists:

union obj
{
	union obj *free_list_link;
	char client_data[1];
};

Obj uses union, which can be regarded as a pointer to another obj of the same form. From the second field, OBJ can be regarded as a pointer to the actual block. The result of using one thing and two things is that it will not cause another kind of waste of memory in order to maintain the pointers necessary for the linked list (this technique is not applicable in strong languages, but it is very common in non strong languages such as C + +)

The realization of free list:

Memory pool

Prepare a large block of memory in advance. Users do not need to apply from the system when they need to, but directly obtain chunk﹐ alloc() from the memory pool to get space for the free list

When the user returns the space to the memory pool, the returned space cannot be directly connected to a large amount of memory because it cannot be determined that the space should be returned first. The memory pool must manage these spaces

Later users get the memory block from the linked list corresponding to the returned small space first, and then go to the large memory block if not found. It can prevent the large memory block from being obtained at a time, so that it can be divided into several small memory blocks without using the large memory requested by the later users
Memory pool operation:

Published 62 original articles, won praise 6, visited 4985
Private letter follow

Tags: less

Posted on Fri, 13 Mar 2020 09:27:18 -0400 by stef686