Space Configurator
1. What is a space Configurator
Efficient management of space (space application and recycling) for each container
2. Why do I need a space Configurator
Various containers - > can store elements - > space is needed at the bottom
new application space
- operator new ---->malloc
- Call constructor ------ complete object construction
Summary of dynamic memory management
In the front container, new is used for every space opening, 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 leakage
- Frequently apply to the system for small blocks of memory, which is easy to cause memory fragmentation, such as: nodes
- Frequent application of small block of memory to the system affects the running efficiency of the program
- Directly use malloc and new to apply, there is extra space waste before each space (malloc will add new things before and after the space applied)
- How to deal with space application failure
- The code structure is confusing and the code reuse rate is not high
- Thread safety not considered
Efficient memory management
3. How to implement SGI – STL space Configurator
Small block memory
- Greater than 128 bytes ----- > large memory
- 128 bytes or less
- The primary space configurator handles large blocks of memory,
- The secondary space configurator processes small blocks of memory.
Primary space Configurator
malloc+free----->set_new_handle
_malloc_alloc_template:
Application space
void * allocate(size_t n) { // Apply for space successfully, return directly, fail to be handled by oom ﹣ malloc void * result =malloc(n); if(nullptr == result) result = oom_malloc(n); return result; }
Release space
static void deallocate(void *p, size_t /* n */) { free(p); }
The processing of "oom" malloc after malloc failure
- Accept function pointer (call set ﹣ new ﹣ handle)
- Verify that the function pointer is empty
- Yes: throw exceptions directly
- Call the function corresponding to the function pointer
- Call malloc to continue to apply for space
- Successful application: direct return
- Application failed: cycle continues
template <int inst> void * __malloc_alloc_template<inst>::oom_malloc(size_t n) { void (* my_malloc_handler)(); void *result; for (;;) { // Check whether the user has set countermeasures for insufficient space. If not, throw exceptions and mode new my_malloc_handler = __malloc_alloc_oom_handler; if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; } // If set, implement the measures to deal with insufficient space provided by users (*my_malloc_handler)(); // Continue to apply for space, you may succeed in applying result = malloc(n); if (result) return(result); } }
set_new_handle
Return value type and parameter type are void * () function pointers
// The parameter of this function is a function pointer, and the return value type is also a function pointer // 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 frequent application of small memory blocks to the system
SGI-STL uses the technology of memory pool to improve the speed of application space and reduce the waste of extra space, and uses hash bucket to improve the speed and efficient management of space acquisition.
Memory pool
The memory pool is: first, apply for a larger memory block for standby. When memory is needed, go directly to the memory pool. When there is not enough space in the pool, then go to the memory to get it. When the user does not use it, return it directly to the memory pool. It avoids the problems of low efficiency, memory fragmentation and extra waste caused by frequent application of small memory to the system
char* _start,*finish
Application space
- Find the right block in the returned memory block
- Find - > split required - > no need - > assign directly
Need to partition the memory block - Not found ----- > apply to memory pool
Defects in application space
- The chain table searches for the right memory block, needs to traverse, and is inefficient
- Division
Pay attention to problems
- When users need space, can they directly intercept from the large space in the memory pool? Why?
A: choose from the list first and take from the large blocks first. If users need large blocks of space, they may not be able to give it - Can the space returned by users be directly spliced in front of large memory?
Answer: No. - How to manage the space returned by users?
A: linked with a linked list - What are the consequences of continuous cutting?
A: memory fragmentation
Design of secondary space Configurator
The secondary space configurator in SGI-STL uses the memory pool technology, but it does not manage the space returned by users in the way of linked list (because the efficiency of searching the appropriate small memory is relatively low when users apply for space), but it uses the hash bucket method to achieve higher management efficiency. Do you need 128 barrels of space to manage the memory blocks returned by users? The answer is no, because the space that the user applies for is almost an integral multiple of 4, and the space of other sizes is rarely used. Therefore, SGI-STL aligns the memory block requested by the user upward to an integer multiple of 8
Multiple of 4 for 32-bit system
Multiple of 8 for 64 bit system
There must be a pointer in each linked list. The 32-bit pointer is 4 bytes, and the 64 bit pointer is 8 bytes
template <int inst> class __default_alloc_template { private: enum { __ALIGN = 8 }; // If the memory required by the user is not an integral multiple of 8, align up to an integral multiple of 8 enum { __MAX_BYTES = 128 }; // Dividing line of size memory block enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; // The number of buckets needed to store small pieces of memory with hash buckets // If the memory block required by the user is not an integral multiple of 8, align up to an integral multiple of 8 static size_t ROUND_UP(size_t bytes) { return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1)); } private: // Use consortia to maintain the structure of linked list - students can think about why there is no structure here union obj { union obj * free_list_link; char client_data[1]; /* The client sees this. */ }; private: static obj * free_list[__NFREELISTS]; // Hash function to find the corresponding bucket number according to the number of bytes provided by the user static size_t FREELIST_INDEX(size_t bytes) { return (((bytes)+__ALIGN - 1) / __ALIGN - 1); } // Start ﹣ free and end ﹣ free are used to mark the start and end positions of large blocks of memory in the memory pool static char *start_free; static char *end_free; // Used to record how many memory blocks the space configurator has requested from the system static size_t heap_size; // ... cross platform operation, encapsulation lock, application space mode, etc };
Management space of secondary space Configurator
void allocate(size_t n)
{
if(n>128)
; / / call the primary space Configurator
1. Calculate the barrel number with n
2. Check whether there is node (internal block) in the bucket
Yes: use the header delete method to return the first memory block
No: return return return (round Fu up (n));
}
Void refer (size_t / is already an integral multiple of 8 /)
{
size_t nobjs =20;
char* chunk = chunk_alloc(n,nobjs);
if(nobjs == 1) / / only one piece is needed
return chunk;
//1<nobjs<=20
Save the first memory, and finally return it to an external user
Hang the remaining nobjs-1 memory block to the 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) //Enough memory pool space to provide 20
{res = start;start+=totalBytes;return res;}
else if(leftBytes>=size)//Less than 20, at least 1
{nobjs =leftBytes/size;res=start;start+=nobjssize;return res;}//How many pieces can be provided
else //There is not enough space in the memory pool, not even one piece can be provided
{
//1. Hang the remaining memory in the memory pool - > to the corresponding bucket
//total_get = 2total_bytes+RoundUP(heap_size>>4);
}
}
-
Application space
// Function function: request space from space Configurator // Parameter n: number of bytes of space required by the user // Return value: returns the first address of the space static void * allocate(size_t n) { obj * __VOLATILE * my_free_list; obj * __RESTRICT result; // Detect that the space required by the user is released more than 128 (that is, whether it is small memory or not) if (n > (size_t)__MAX_BYTES) { // It's not a small block of memory, it's handled by the primary space configurator return (malloc_alloc::allocate(n)); } // Find the corresponding bucket number according to the bytes required by the user my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; // If there is no memory block in the bucket, add space to the bucket if (result == 0) { // Align n up to the integer quilt of 8 to ensure that when the memory block is replenished to the bucket, the memory block must be an integer multiple of 8 void *r = refill(ROUND_UP(n)); return r; } // Maintain the chain relationship of the remaining memory blocks in the bucket *my_free_list = result->free_list_link; return (result); };
-
Recycling space
No free space in secondary space Configurator
// Function function: user returns space to space Configurator // Parameter: total space size of the first address n of p space static void deallocate(void *p, size_t n) { obj *q = (obj *)p; obj ** my_free_list; // If the space is not a small block of memory, it should be recycled by the primary space configurator if (n > (size_t)__MAX_BYTES) //More than 128, released according to the primary space configuration { malloc_alloc::deallocate(p, n); return; } //Not until 128 // Find the corresponding hash bucket and hang the memory in the corresponding hash bucket my_free_list = free_list + FREELIST_INDEX(n); q->free_list_link = *my_free_list; *my_free_list = q; }
- Replenish memory
template <int inst> char* __default_alloc_template<inst>::chunk_alloc(size_t size, int& nobjs) { // Calculate the total size of nobjs size byte memory block and the total size of the remaining space in the memory pool char * result; size_t total_bytes = size * nobjs; size_t bytes_left = end_free - start_free; // If the memory pool can provide a total of bytes, return if (bytes_left >= total_bytes) { result = start_free; start_free += total_bytes; return(result); } else if (bytes_left >= size) { // nobjs block cannot be provided, but at least one size byte memory block can be provided, and it will return nobjs = bytes_left / size; total_bytes = size * nobjs; result = start_free; start_free += total_bytes; return(result); } else { // Insufficient memory pool space, not even a small village // Ask the system heap for help and replenish the memory pool // Calculate the size of supplementary space to memory: double the total size of this space + apply to the system for the total size / 16 size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); // If the memory pool has space left (the space must be an integer multiple of 8), hang the space to the corresponding hash bucket if (bytes_left > 0) { // Find the right hash bucket and hang the remaining space on it 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; } // Replenish space to the memory pool through the system heap. If the replenishment is successful, recursion continues to allocate start_free = (char *)malloc(bytes_to_get); if (0 == start_free) { // Failed to replenish the space through the system heap. Check whether there is a large memory block in the hash bucket 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 yes, add the memory block to the memory pool, and continue to allocate recursively if (0 != p) { *my_free_list = p->free_list_link; start_free = (char *)p; end_free = start_free + i; return(chunk_alloc(size, nobjs)); } } // The mountain and the water are exhausted. You can only ask the first level space configurator for help // Note: you must leave end ﹣ free blank here, because once the primary space configurator throws an exception, there will be problems end_free = 0;//End? Free marks the end of memory pool space start_free = (char *)malloc_alloc::allocate(bytes_to_get); } // Replenish space to memory pool through system heap succeeded, update information and continue allocation heap_size += bytes_to_get; end_free = start_free + bytes_to_get; return(chunk_alloc(size, nobjs)); } }
Default selection of space Configurator
By default, SGI-STL uses the primary or secondary space configurator, which is controlled through the use? Malloc macro:
#ifdef __USE_MALLOC typedef malloc_alloc alloc; typedef malloc_alloc single_client_alloc; #else // Secondary space configurator definition #endif
This macro is not defined in SGI STL, so SGI STL uses the secondary space configurator by default
Re encapsulation of space Configurator
In C + +, the space required by users may be of any type, with single object space and continuous space. It is not very friendly to let users calculate the total space required by themselves every time, so SGI-STL re encapsulates the space configurator
template<class T, class Alloc> class simple_alloc { public: // Request space of n objects of type T static T *allocate(size_t n) { return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof (T)); } // Request a space of T type object size static T *allocate(void) { return (T*)Alloc::allocate(sizeof (T)); } // Free space of n objects of type T static void deallocate(T *p, size_t n) { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); } // Free a space of T type object size static void deallocate(T *p) { Alloc::deallocate(p, sizeof (T)); } };
Object construction and release
For efficiency reasons, SGI-STL decided to separate the process of space application release and object construction and analysis, because some objects do not need to call destructors for construction, and destructors are not needed for destruction, which can improve the performance of the program
// When returning space, first call this function to clean up the resources in the object template <class T> inline void destroy(T* pointer) { pointer->~T(); } // After the space application is good, call the function: use placement-new to complete the object's construction. template <class T1, class T2> inline void construct(T1* p, const T2& value) { new (p)T1(value); }