Memory pool summary

Memory pool summary

Classic memory pool implementation

reference resources:
https://www.ibm.com/developerworks/cn/linux/l-cn-ppp/index6.html
https://blog.csdn.net/K346K346/article/details/49538975

  1. Classic memory pool implementation process
    (1) First, a continuous memory space is applied, which can hold a certain number of objects;
    (2) Each object, together with a pointer to the next object, constitutes a Memory Node. Each free Memory Node forms a linked list through a pointer, and each Memory Node of the linked list is a piece of memory space available for allocation;
    (3) Once a memory node is allocated, it is removed from the list of free memory nodes;
    (4) Once the space of a memory node is released, the node is added to the free memory node list again;
    (5) If all memory nodes of a memory block are allocated, if the program continues to apply for new object space, a memory block will be applied again to hold the new object. The newly applied memory block will be added to the memory block list.

The implementation process of the classic memory pool is roughly as described above, and its visualization process is as shown in the following figure:

As shown in the figure above, the requested memory block holds three available free nodes for allocation. The free node is managed by the free node list. If it is allocated, it is deleted from the free node list. If it is released, it is reinserted into the head of the list. If there are not enough free nodes in the memory block, reapply the memory block, and the requested memory block is managed by the memory block list.

Note that the insertion of memory block linked list and free memory node linked list involved in this paper. In order to avoid traversing the linked list to find the tail node and facilitate operation, the insertion of new nodes is inserted into the head of the linked list, not the tail.

  1. Classic memory pool data structure design

According to the above process design, the memory pool class template has several members.

Two pointer variables:

Memory block chain header pointer: pmebblockheader;
Free node chain header pointer: pFreeNodeHeader;

Free node structure:

struct FreeNode
{
	FreeNode* pNext;
	char data[ObjectSize];
};

Memory block structure:

struct MemBlock
{
	MemBlock *pNext;
	FreeNode data[NumofObjects];
};

  1. Implementation of classical memory pool
    According to the above classic memory pool design, the coding is implemented as follows.
#include <iostream>
using namespace std;

template<int ObjectSize, int NumofObjects = 20>
class MemPool
{
private:
	//Free node structure
	struct FreeNode
	{
		FreeNode* pNext;
		char data[ObjectSize];
	};

	//Memory block structure
	struct MemBlock
	{
		MemBlock* pNext;
		FreeNode data[NumofObjects];
	};

	FreeNode* freeNodeHeader;
	MemBlock* memBlockHeader;

public:
	MemPool()
	{
		freeNodeHeader = NULL;
		memBlockHeader = NULL;
	}

	~MemPool()
	{
		MemBlock* ptr;
		while (memBlockHeader)
		{
			ptr = memBlockHeader->pNext;
			delete memBlockHeader;
			memBlockHeader = ptr;
		}
	}
	void* malloc();
	void free(void*);
};

//Assign idle nodes
template<int ObjectSize, int NumofObjects>
void* MemPool<ObjectSize, NumofObjects>::malloc()
{
	//No free nodes, request new memory block
	if (freeNodeHeader == NULL)
	{
		MemBlock* newBlock = new MemBlock;
		newBlock->pNext = NULL;

		freeNodeHeader=&newBlock->data[0];	 //Set the first node of the memory block as the first node of the free node list
		//String other nodes of a memory block
		for (int i = 1; i < NumofObjects; ++i)
		{
			newBlock->data[i - 1].pNext = &newBlock->data[i];
		}
		newBlock->data[NumofObjects - 1].pNext=NULL;

		//First request memory block
		if (memBlockHeader == NULL)
		{
			memBlockHeader = newBlock;
		}
		else
		{
			//Add a new memory block to the memory block list
			newBlock->pNext = memBlockHeader;
			memBlockHeader = newBlock;
		}
	}
	//Returns the first node of the empty node free list
	void* freeNode = freeNodeHeader;
	freeNodeHeader = freeNodeHeader->pNext;
	return freeNode;
}

//Release assigned nodes
template<int ObjectSize, int NumofObjects>
void MemPool<ObjectSize, NumofObjects>::free(void* p)
{
	FreeNode* pNode = (FreeNode*)p;
	pNode->pNext = freeNodeHeader;	//Insert the released node into the free node head
	freeNodeHeader = pNode;
}

class ActualClass
{
	static int count;
	int No;

public:
	ActualClass()
	{
		No = count;
		count++;
	}

	void print()
	{
		cout << this << ": ";
		cout << "the " << No << "th object" << endl;
	}

	void* operator new(size_t size);
	void operator delete(void* p);
};

//Defining memory pool objects
MemPool<sizeof(ActualClass), 2> mp;

void* ActualClass::operator new(size_t size)
{
	return mp.malloc();
}

void ActualClass::operator delete(void* p)
{
	mp.free(p);
}

int ActualClass::count = 0;

int main()
{
	ActualClass* p1 = new ActualClass;
	p1->print();

	ActualClass* p2 = new ActualClass;
	p2->print();
	delete p1;

	p1 = new ActualClass;
	p1->print();

	ActualClass* p3 = new ActualClass;
	p3->print();

	delete p1;
	delete p2;
	delete p3;
}

Program running results:

004AA214: the 0th object
004AA21C: the 1th object
004AA214: the 2th object
004AB1A4: the 3th object
  1. Program analysis

Read the above procedures, should pay attention to the following points.
(1) For a specific class object, the size of the memory block in the memory pool is fixed, and the size of the memory Node is also fixed. The memory block is divided into multiple memory nodes at the beginning of application, and the size of each Node is ItemSize. At first, all memory nodes are idle and linked.

(2) Member pointer variable memBlockHeader is used to connect all applied memory blocks into a memory block list, so that all applied memory can be released through it. freeNodeHeader variable is to string all free memory nodes into a linked list. If freeNodeHeader is empty, there is no free memory node available. You must request a new memory block.

(3) The process of applying for space is as follows. In the case that the free memory node list is not empty, the malloc process only takes the first node of the free memory node list from the list, and then moves the chain header pointer to the next node. Otherwise, it means a new memory block is needed. This process needs to apply for new memory blocks to be cut into multiple memory nodes and string them together. The main cost of memory pool technology is here.

(4) The process of releasing an object is to insert the released memory node back into the beginning of the memory node list. The last node to be released is the next one to be allocated.

(5) The speed of memory pool technology to request / release memory is very fast. In most cases, the complexity of memory allocation is O(1). The main overhead is to generate new memory blocks when freeNodeHeader is empty. The complexity of memory node release process is O(1).

(6) In the above program, pointers p1 and p2 apply for space twice in a row. The difference between the addresses they represent is 8, which is exactly the size of a memory node. After the object pointed to by pointer p1 is released, the space is applied again, and the resulting address is exactly the same as that just released. The address represented by pointer p3 is far away from the address of the first two objects, because the free memory node in the first memory block has been allocated, and the object pointed to by p3 is located in the second memory block.

The above memory pool scheme is not perfect, for example, only a single application object space, not an array of application objects, the number of memory blocks in the memory pool can only be increased, not reduced, and multi-threaded security is not considered.

std::allocator of standard library

Header file

#include <memory>

Class template declaration

reference resources: https://docs.microsoft.com/zh-cn/cpp/standard-library/allocator-class?view=vs-2019

template< class T >
struct allocator;

member

type
const_pointer Type of constant pointer to object type managed by allocator
const_reference Type of constant reference to object type managed by allocator
difference_type Signed integer that represents the difference between pointer values to object types managed by the allocator
pointer Provides the type of pointer to the object type managed by the allocator
reference Provides the type of reference to the object type managed by the allocator
size_type An unsigned integer type that represents the length of any sequence that an object of type allocator can allocate.
value_type Type managed by the allocator.
Member function
allocator Constructor used to create allocator object
address Finds the address of the object whose value is specified
allocate A block of memory large enough to hold at least a specified number of elements
construct Constructs a specific type of object at the specified address initialized with the specified value
deallocate Releases a specified number of objects from storage starting at a specified location
destroy Call the object destructor without freeing the memory of the stored object
max_size Returns the number of elements of Type type that can be allocated by an allocator like object before available memory runs out
rebind Enables one type of object allocator to allocate storage structures to another
operator= Assign one allocator object to another

realization

reference resources: https://blog.csdn.net/xiyanggudao/article/details/51543839

  • A simple implementation is as follows:
    Simple implementation of encapsulation of operator new and operator delete
template<class T>
class allocator
{
public:
    // 1. Why do you need the following members? What's the role?
    typedef T          value_type;
    typedef T*         pointer;
    typedef const T*   const_pointer;
    typedef T&         reference;
    typedef const T&   const_reference;
    typedef size_t     size_type;       // size_t is an unsigned integer
    // ptrdiff_t is a signed integer representing the type of result of pointer subtraction
    typedef ptrdiff_t  difference_type;
 
    // 2. What's this for? Why is template U, not the same as allocator's T?
    template<class U>
    struct rebind
    {
        typedef allocator<U> other;
    };
 
    // Default constructor, do nothing
    allocator() noexcept
    {
    }
 
    // Generalized constructors, do nothing
    // 3. Why do we need this generalized constructor? Is it appropriate to copy different types of allocator s?
    template<class U>
    allocator(const allocator<U>&) noexcept
    {
    }
 
    // Destructor, do nothing
    ~allocator() noexcept
    {
    }
 
    // Return object address
    pointer address(reference val) const noexcept
    {
        //The non const version calls the const version, see article 3 of Effective C + +
        return const_cast<reference>(address(static_cast<const_reference>(val)));
    }
 
    // Return object address
    const_pointer address(const_reference val) const noexcept
    {
        return &val;
    }
 
    // Apply for memory. count refers to the number of objects, not bytes.
    // 4. What does hint do?
    pointer allocate(size_type count, allocator<void>::const_pointer hint = nullptr)
    {
        return static_cast<pointer>(::operator new(count * sizeof(value_type)));
    }
 
    // Free memory
    void deallocate(pointer ptr, size_type count)
    {
        ::operator delete(ptr);
    }
 
    // Maximum number of configurable objects, not bytes
    size_type max_size() const noexcept
    {
        return (static_cast<size_type>(-1) / sizeof(value_type));
    }
 
    // Construction object, Args is the template parameter package, see section 16.4 of version 5 of C++ Primer
    template <class U, class... Args>
    void construct(U* p, Args&&... args)
    {
        ::new ((void *)p) U(::std::forward<Args>(args)...);
    }
 
    // Analytic object
    template <class U>
    void destroy(U* p)
    {
        p->~U(); // The original template can also be used in this way
    }
};
 
// 5. Why do you want to specialize void?
template<>
class allocator<void>
{
public:
    typedef void value_type;
    typedef void *pointer;
    typedef const void *const_pointer;
    template <class U> struct rebind
    {
        typedef allocator<U> other;
    };
};

Q & A
1. STL specification, and these type s are useful in iterator and traits technologies.
2. This structure can be used to allocate memory for a type different from the element type of the implemented container.
Member class templates define other types. Its only purpose is to provide type name allocator when giving type name allocator < type ><_ Other>
For example, given the allocator object al type A, you can use an expression to allocate_ Object of type Other:
A::rebind::other(al).allocate(1, (Other *)0)
Alternatively, you can write a type to name its pointer type:
A::rebind::other::pointer
3. There is only one template parameter of the allocator class, which represents the allocated element type. If the allocator encapsulates only the allocation strategy of memory and has nothing to do with the element type, it seems reasonable to define the generic replication structure. At the same time, if it is not defined as the generic rebind, it cannot be used. The construct member function and destroy member function are also generics, and the use condition of allocator is very loose.
4,hint
Either 0 or a value previously obtained by another call to allocate and not yet freed with deallocate.
When it is not 0, this value may be used as a hint to improve performance by allocating the new block near the one specified. The address of an adjacent element is often a good choice.
5. Only void * variable, no void variable, no void & variable, cannot typedef void value_type and so on.

Use example

#include <memory>
#include <iostream>
#include <string>
 
int main()
{
    std::allocator<int> a1;   // default allocator for ints
    int* a = a1.allocate(1);  // space for one int
    a1.construct(a, 7);       // construct the int
    std::cout << a[0] << '\n';
    a1.deallocate(a, 1);      // deallocate space for one int
 
    // default allocator for strings
    std::allocator<std::string> a2;
 
    // same, but obtained by rebinding from the type of a1
    decltype(a1)::rebind<std::string>::other a2_1;
 
    // same, but obtained by rebinding from the type of a1 via allocator_traits
    std::allocator_traits<decltype(a1)>::rebind_alloc<std::string> a2_2;
 
    std::string* s = a2.allocate(2); // space for 2 strings
 
    a2.construct(s, "foo");
    a2.construct(s + 1, "bar");
 
    std::cout << s[0] << ' ' << s[1] << '\n';
 
    a2.destroy(s);
    a2.destroy(s + 1);
    a2.deallocate(s, 2);
}

Implementation of memory pool in SGI STL

The principle of memory pool is simply to apply a large amount of memory to the system at one time, and then when there is a memory request, if the memory size of the memory pool can meet the request, it will be allocated from the memory pool without system call, so as to achieve performance improvement. Multiple memory application system calls are easy to generate memory fragments and cause memory waste

________________________________________________ Reference: STL source code analysis

  • STL memory allocation can be divided into primary and secondary allocators. The primary allocator uses malloc to allocate memory and the secondary allocator uses memory pool.
  • When the required memory size is greater than 128, malloc is called directly to allocate memory. Otherwise, malloc is allocated from the free list. In this way, the memory fragmentation caused by too many small memory blocks and the additional burden of managing memory can be avoided, resulting in low memory utilization.

**Memory pool in SGI: * * start with two pointers_ Free and end_free marks the contiguous heap space of its scope, and is not responsible for memory allocation and recovery.
The specific work of memory allocation and recovery is handed over to an array + linked list structure, as shown in the following figure:

  • First level Configurator__ malloc_alloc_template
//Malloc based allocator. Usually slower than the default alloc ator later
//Generally speaking, it's thread sate, and the use of space is more advanced
//Here is the first level Configurator
//Note that there is no "template type parameter" and "non type parameter" inst is not useful at all
template<int inst>
class __malloc_alloc_template{
private:
//The following are function pointers, which are used to deal with low memory situations
//oom:out of memory
static void *oom_malloc(size_t);
static void *oom_realloc(void*,size_t);
static void (*__malloc_alloc_oom_handler)();

public:
static void *allocate(size_t n){
    void *result=malloc(n);//The first level configurator uses malloc()
    //When the following requirements cannot be met, use oom instead_ malloc()
    if(0==result) result==oom_malloc(n);
    return result;
}
static void deallocate(void *p,size_t){
    free(p);//The first level configurator uses free()
}
static void* reallocate(void *p,size_t,size_t new_sz){
    void *result=realloc(p,new_sz);//The first level configurator uses realloc() directly
    //When the following requirements cannot be met, use oom instead_ realloc()
    if(0==result) result=oom_realloc(p,new_sz);
    return result;
}
//Set of the following simulation C + +_ new_ Handler (), through which you can specify your own out of memory handler
static void(*set_malloc_handler(void(*f)()))()
{
    void(*old)()=__malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler=f;
    return (old);
}
};

//malloc_alloc out-of-memory handling
//Initial value is 0, to be set by the client
template<int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)()=0;

template<int inst>
void * __malloc_alloc_template<inst>::oom_malloc(size_t n){
    void(*my_malloc_handler)();
    void *result;
    for(;;){//Keep trying to release, configure, release, configure
        my_malloc_handler=__malloc_alloc_oom_handler;
        if(0==my_malloc_handler){
            __THROW_BAD_ALLOC;
        }
        (*my_malloc_handler)();//Calling a handler in an attempt to free memory
        result=malloc(n);//Try to configure memory again
        if(result) return(result);
    }
}

template<int inst>
void * __malloc_alloc_template<inst>::oom_realloc(void *p,size_t n){
    void (*my_malloc_handler)();
    void *result;
    for(;;){//Keep trying to release, configure, release, configure
        my_malloc_handler=__malloc_alloc_oom_handler;
        if(0==my_malloc_handler){__THROW_BAD_ALLOC;}
        (*my_malloc_handler)();//Calling a handler in an attempt to free memory
        result=realloc(p,n);//Try to configure memory again
        if(result) return(result);
    }
}
//Note that the parameter inst is specified as 0 directly below
typedef __malloc_alloc_template<0> malloc_alloc;
  • Secondary Configurator__ default_alloc_template
    The approach of SGI's second level configurator is that if the block is large enough and exceeds 128bytes, it will be transferred to the first level configurator for processing. If the block is smaller than 128bytes, then internal storage pool management: configure a large block of memory each time and maintain the corresponding free list. If you need the same amount of memory next time, pull it out of free lists. If the client releases small blocks, they are recycled to free lists 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 (for example, if the client requires 30bytes, it will automatically adjust it to 32bytes), and maintain 16 free list s, with their respective management sizes of 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96104112120128bytes

  • free_ The list node structure is as follows:
union obj{
    union obj* free_list_link;
    char client_data[1];
}

union can only use one member at a time, free_list_link to the address of the next memory block, client_data points to the memory block first address.
About why we add char client here_ Data [1], I agree with the following answer
https://blog.csdn.net/w450468524/article/details/51649222

because free_list_link The length is large, union obj Is an integral multiple of its length, client_data[1]It takes up only one byte of its head. therefore client_data The first address points to the whole union obj The first address of, that is, the address of the actual block.
//Simple test:
#include
#include
union obj{
union obj * free_list_link;
char client_data[1]; /* The client sees this.*/
};
int main(){
obj * block = (obj ) malloc(128);
printf("%x\n", block);
printf("%x\n", block->client_data);
return 0;
}
//The results are as follows:
1c9c010
1c9c010
**here client_data In fact, it is used as a pointer type conversion for convenience. General memory buffer We all use char Pointer, if you want to use obj Plus (char )myBlock Cast. On the contrary, direct use myBlock->client_data that will do*,It's much more convenient.

The following is part of the implementation of the second level configurator:

enum{__ALIGN=8};//Upward boundary of small block
enum{__MAX_BYTES=128};//Upper limit of small blocks
enum{__NFREELISTS=__MAX_BYTES/__ALIGN};//Number of free lists

//Here is the second level Configurator
//Note that there is no 'template type parameter' and the second parameter is completely useless
//The first parameter is used in multithreaded environment,
template<bool threads,int inst>
class __default_alloc_template{
private:
    //ROUND_UP() increases bytes to a multiple of 8
    static size_t ROUND_UP(size_t bytes){
        return (((bytes)+__ALIGN-1)&~(__ALIGN-1));//Add 7 first, and then reset the lower 3 bits
    }
private:
    union obj{//Node construction of free lists
        union obj* free_list_link;
        char client_data[1];
    };
private:
    //16 free lists
    static obj* volatile free_list[__NFREELISTS];
    //The following function determines to use the n-th free list based on the block size. N from 1
    static size_t FREELIST_INDEX(size_t bytes){
        return (((bytes)+__ALIGN-1)/__ALIGN-1);
    }

    //Returns an object of size n, and may add other blocks of size n to the free list
   static void *refill(size_t n);
   //Configure a large space to hold nobjs blocks of size "size"
   //If it is inconvenient to configure nobjs blocks, nobjs may be reduced
   static char *chunk_alloc(size_t size,int &nobjs);
   //chunk allocation state
   static char *start_free;//Memory pool start location, only in chunk_ Change in alloc()
   static char *end_free;//End of memory pool, only in chunk_ Changes in alloc ()
public:
    //
    static void *allocate(size_t n);
    static void *deallocate(void *p,size_t n);
    static void *reallocate(void *p,size_t old_sz,size_t new_sz);   
};
//Definition and initial value setting of static data member
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::start_free=0;
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::end_free=0;
template <bool threads,int inst>
char *__default_alloc_template<threads,inst>::heap_size=0;

template <bool threads,int inst>
__default_alloc_template<threads,inst>:obj * volatile
__default_alloc_template<threads,inst>::free_list[_NFREELISTS]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
  • Space configuration function allocate()
    First determine the size of the block, larger than 128bytes, then call the first level space configurator. If 128bytes is less than free, check the corresponding free list.. If there are available blocks between free list, it will be used directly. If there is no available block, the block size will be raised to the multiple boundary of the number, then refill().
static void * allocate(size_t n)
{
    obj * volatile * my_free_list;    //volatile keyword prevents the program from taking value from the register, and the secondary pointer points to the pointer array.
    obj * result;
    //If it is greater than 128byte, apply directly to heap memory.
    if( n > (size_t)__MAX_BYTES)
    {
        return (malloc_alloc::allocate(n));
    }
    
    //Otherwise, apply to the free list. If the free list is insufficient, apply directly to the memory pool.
    //Find the right one of 16 free lists
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if(result == 0)
    {
       //No free list found, ready to repopulate free list
        void *r = refill(ROUND_UP(n));
        return r;
    }
    //Adjust free list
    *my_free_list = result->free_list_link;
    return (result);
}

  • deallocate() space release function
    First determine the block size. If it is greater than 128bytes, call the first level configurator. If it is less than 128bytes, find the corresponding free list and recycle the block
//p cannot be 0
static void deallocate(void *p,size_t n)
{
    obj *q=(obj*)p;
    obj *volatile *my_free_list;
    //Call the first level configurator when it is greater than 128
    if(n>(size_t) __MAX_BYTES){
        malloc_alloc::deallocate(p,n);
        return ;
    }
    //Find the corresponding free list
    my_free_list =free_list+FREELIST_INDEX(n);
    //Adjust free list and recycle block
    q->free_list_link=*my_free_list;
    *my_free_list=q;
}

  • Repopulate the free lists function refill()
    When allocate() is used to configure the space, when there is no available block in the free list, refill() is called to repopulate the space for the free list. The new space will be taken from the memory pool (call chunk_alloc()). By default, 20 new nodes are obtained, but in case of insufficient memory pool space, the number of nodes (blocks) obtained may be less than 20:
//Returns an object of size n, and sometimes adds nodes for the appropriate free list
//Assume that n has been properly raised to a multiple of 8
template<bool threads,int inst>
void* __default_alloc_template<threads,inst>::refill(size_t n)
{
    int nobjs=20;
    //Call chunk_alloc(), trying to get nobjs blocks as new nodes of free list
    //Note that the parameter nobjs is pass by reference
    char * chunk=chunk_alloc(n,nobjs);
    obj* volatile* my_free_list;
    obj* result;
    obj* current_obj,*next_obj;
    int i;
    
    //If only one block is obtained, the block is allocated to the caller. There is no new node in the free list
    if(1==nobjs) return(chunk);
    //Otherwise, prepare to adjust the free list to include the new node
    my_free_list=free_list+FREELIST_INDES(n);
    
    //Create a free list in the chunk space as follows
    result=(obj*) chunk;//This piece is ready to return to the customer
    //The following guides the free list to the newly configured space (taken from the memory pool)
    *my_free_list=next_obj=(obj*)(chunk+n);
    //Connect the nodes of free list in series as follows
    for(i=1;;i++){//Starting from 1, because the 0 will be returned to the client
        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);
}

  • Memory pool
    Take space from the memory pool for free list. It's chunk_ The work of alloc():
    Here the memory pool just uses two pointers to start_free and end_free marks a continuous heap space of its scope, and is not responsible for memory allocation and recovery. The specific work of memory allocation and recycling is handed over to an array + linked list structure for processing
//Assume that the size has been appropriately increased to a multiple of 8
//Note that the parameter nobjs is pass by reference
template<bool threads,int inst>
char* __default_alloc_template<threads,inst>::chunk_alloc(size_t size,int & nobjs)
{
    char* result;
    size_t total_bytes=size*nobjs;
    size_t bytes_left=end_free-start_free;//Memory pool remaining space
    if(bytes_left>=total_bytes){
        //The remaining space of memory pool fully meets the demand
        result=start_free;
        start_free+=total_bytes;
        return(result);
    }else if(bytes_left>=size){
        //The remaining space of the memory pool cannot fully meet the demand, but it is enough to supply more than one (including) block
        nobjs=bytes_left/size;
        total_bytes=size* nobjs;
        result=start_free;
        start_free+=total_bytes;
        return(result);
    }else{
        //The remaining space in the memory pool cannot provide the size of a chunk
        size_t bytes_to_get=2*total_bytes+ROUND_UP(heap_size>>4);
        //Try to make the remaining bits in the memory pool useful
        if(bytes_left>0){
        //There are still some small bits in the memory pool. First, allocate the appropriate free list
        //First, look for the appropriate free list, where bytes_left is a multiple of 8
        obj* volatile* my_free_list=free_list+FREELIST_INDEX(bytes_left);
        //Adjust the free list to include the remaining space in the memory pool
        ((obj*)start_free)->free_list_link=*my_free_list;
        *my_free_list=(obj*)start_free;
    }
    //Configure heap space to supplement memory pool
    start_free=(char*)malloc(bytes_to_get);
    if(0==start_free){
        //Insufficient heap space, malloc() failed
        int i;
        obj* volatile* my_free_list,*p;
        //Try to check what we have on hand. It won't hurt. We're not going to try to configure it
        //Smaller chunks, because that's prone to disaster on multiprocessing machines
        //Search for the appropriate free list below
        //The so-called appropriateness refers to the free list of "there are unused blocks, and the blocks are large enough"
        for(i=size;i<=__MAX_BYTES;i+=__ALIGN){
          my_free_list=free_list+FREELIST_INDEX(i);
          p=*my_free_list;
          if(0!=p){
             //Adjust the free list to release unused blocks
             *my_free_list=p->free_list_link;
             start_free=(char*)p;
             end_free=start_free+i;
             //Call yourself recursively to fix nobjs
             return (chunk_alloc(size,nobjs));
             //Note that any remaining bits will eventually be added to the appropriate free list for future use
           }
        }
      end_free=0;//If something goes wrong (no memory available anywhere)
      //Call the first level configurator to see out_ Of_ Can memory mechanism do its best
      start_free=(char*)malloc_alloc::allocate(bytes_to_get);
      //This can result in an exception being thrown, or an improvement in the lack of memory
    }
    heap_size+=bytes_to_get;
    end_free=start_free+bytes_to_get;
    //Call yourself recursively to fix nobjs
    return (chunk_alloc(size,nobjs));
    }
}

  • Make alloc the second level Configurator
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc;
  • SGI packs another simple for alloc_ The alloc interface is as follows, so that the configurator interface can meet the STL specification
//SGI wraps another interface for alloc as follows, so that the interface of configurator can meet the STL specification
template<class T,class Alooc>
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(sizeof(T));
    }
    static void deallocate(T *p,size_t n){
        if(0!=)Alloc::deallocate(p,n*sizeof(T));
    }
    static void deallocate(T *p){
        Alloc::deallocate(p,sizeof(T));
    }
};

All containers of SGI STL use this simple_alloc interface, for example:

template <class T,class Alloc=alloc>//Use alloc as the configurator by default
class vector{
protected:
//Exclusive space configurator, one element size at a time
typedef simple_alloc<value_type,Alloc> data_allocator;

void deallocate(){
    if(...)
        data_allocator::deallocate(start,end_of_storage-start);
}
...
}

Tags: less Linux

Posted on Thu, 04 Jun 2020 09:11:08 -0400 by crazytoon