The principle and implementation of memory pool technology

Source: Internet
Author: User

6.1 The principle of custom memory pool performance optimization

As mentioned earlier, the reader has learned the difference between "heap" and "stack". In programming practice, it is inevitable to use a lot of memory on the heap. For example, when maintaining the data structure of a linked list in a program, each time a node that adds or deletes a linked list needs to allocate or release a certain amount of memory from the memory heap, and when maintaining a dynamic array, a new memory space is allocated on the memory heap if the size of the dynamic array does not meet the needs of the program.

6.1.1 Default memory management function is insufficient

Using the default memory management function New/delete or malloc/free allocates and frees memory on the heap with some additional overhead.

When the system receives a request to allocate a certain amount of memory, it first looks for the internally maintained memory free block table, and needs to be based on a certain algorithm (such as allocating a memory block that is not smaller than the requested size first, or allocating the memory block that is most suitable for the request size. or allocate the maximum free memory block, etc.) to find a suitable size of free memory block. If the free memory block is too large, it needs to be cut into the allocated part and the smaller free block. The system then updates the memory free block table to complete a memory allocation. Similarly, when the memory is freed, the system re-joins the freed memory block to the table of free memory blocks. If possible, the adjacent free blocks can be combined into larger free blocks.

The default memory management function also takes into account the multi-threaded application, which needs to be locked each time the memory is allocated and freed, adding overhead as well.

It can be seen that if the application frequently allocates and frees memory on the heap, it will result in a loss of performance. It also causes a large amount of memory fragmentation in the system, reducing the utilization of memory.

The default allocation and release memory algorithms naturally take performance into account, however, the general version of these memory management algorithms requires more extra work in order to cope with more complex and broader situations. For a specific application, a custom memory pool that is appropriate for its own specific memory allocation release pattern can achieve better performance.

6.1.2 Definition and classification of memory pools

The idea of a custom memory pool through this "pool" word, the application can use the system's memory allocation call pre-one time to request the appropriate size of memory as a memory pool, then the application's own memory allocation and release can be done through this memory pool. The memory allocation function of the system needs to be called only when the memory pool size needs to be expanded dynamically, and all other memory operations are under the control of the application.

Application-Customized memory pools have different types depending on the applicable scenario.

From a thread-safe point of view, a pool of memory can be divided into single-threaded memory pools and multi-threaded memory pools. The entire lifecycle of a single-threaded memory pool is only used by one thread, so there is no need to consider mutually exclusive access issues, and multithreaded memory pools may be shared by multiple threads, so you need to lock each time you allocate and free memory. In contrast, single-threaded memory pools have higher performance, and multi-threaded memory pools have a wider range of applications.

The memory pool can be allocated from the memory cell size to divide, can be divided into fixed memory pool and variable memory pool. The so-called fixed memory pool is that each time the application from the memory pool allocated memory unit size has been determined beforehand, is fixed, and the variable memory pool each allocation of memory unit size can change on demand, wider application, and performance than fixed memory pool lower.

Example of how the 6.1.3 memory pool works

The following is an example of a fixed memory pool that illustrates how the memory pool works, as shown in 6-1.


Figure 6-1 Fixed Memory pool

A fixed memory pool consists of a series of fixed-size blocks of memory, each of which contains a fixed number and size of memory units.

6-1, the memory pool contains 4 blocks of memory altogether. When the memory pool is first generated, only one memory block is applied to the system, and the returned pointer acts as the head pointer for the entire memory pool. Then, as the application's memory needs continue to grow, the memory pool needs to be dynamically expanded to request new chunks of memory to the system and link all the blocks of memory to the pointer. For the operating system, it has allocated 4 equal-sized blocks of memory for the application. Because the size is fixed, so the speed of allocation is faster, and for the application, its memory pool opens up a certain size, the memory pool has the remaining space inside.

For example, zoom in to the 4th memory block, which contains a portion of the memory pool header information and 3 memory pool units of equal size. Unit 1 and Unit 3 are idle, and Unit 2 is already assigned. When an application needs to allocate a cell-sized memory through the memory pool, simply traverse through all the memory pool header information and quickly navigate to the memory pool block that has the idle unit. Then, according to the block information directly to the 1th free cell address, return this address, and mark the next free unit, when the application releases a memory pool unit, directly in the corresponding memory pool block information to mark the memory unit as a free unit.

It can be seen that the memory pool operates very quickly compared to the system management memory, and its advantages in performance optimization are as follows.

(1) For special cases, such as the need to frequently allocate the release of fixed-size memory objects, there is no need for complex allocation algorithm and multi-threaded protection. There is also no need to maintain the extra overhead of memory idle tables for higher performance.

(2) Due to the opening up a certain number of contiguous memory space as a memory pool block, thus to some extent, improve the program locality, improve program performance.

(3) It is easier to control page boundary alignment and memory byte alignment, without the problem of memory fragmentation.

6.2 Implementation instance of a memory pool

This section analyzes a memory pool implementation that is actually applied to a large application, and explains in detail how it is used and how it works. This is a memory pool that is applied to a single-threaded environment and has a fixed allocation unit size, which is typically used to allocate memory for class objects or structs that are created frequently and may be created more than once.

This section first explains the data structure declaration and diagram of the memory pool, and then describes its principle and behavior characteristics. It then explains the implementation details, and finally describes how to apply this memory pool to the actual program and compare it with the program performance that uses the normal memory function to request memory.

6.2.1 Internal structure

The memory pool class MemoryPool is declared as follows:

Class Memorypool{private:    memoryblock*   pblock;    USHORT          nunitsize;    USHORT          ninitsize;    USHORT          ngrowsize;public:                     memorypool (USHORT nunitsize,                                  USHORT ninitsize = 1024x768,                                  USHORT ngrowsize = );                    ~memorypool ();    void*           Alloc ();    void free            (void* p);};

Memoryblock is a struct in the memory pool that is attached to the head of memory that is really used to allocate memory for memory requests, and it describes the usage information for the memory blocks that are associated with it:

struct memoryblock{    USHORT          nSize;    USHORT          Nfree;    USHORT          Nfirst;    USHORT          nDummyAlign1;    memoryblock*  Pnext;    Char            adata[1];static void* operator new (size_t, USHORT ntypes, USHORT nunitsize) {return:: operator new (sizeof ( Memoryblock) + ntypes * nunitsize);} static void  operator delete (void *p, size_t) {:: operator delete (p);} Memoryblock (USHORT ntypes = 1, USHORT nunitsize = 0); ~memoryblock () {}};

The data structure of this memory pool is shown in 6-2.


Figure 6-2 Data structure of the memory pool

6.2.2 General mechanism

The overall mechanism for this memory pool is as follows.

(1) During operation, the MemoryPool memory pool may have multiple memory blocks that are used to satisfy memory request requests, which are a large contiguous area of memory from the process heap, consisting of a memoryblock struct and multiple allocated memory units. All memory blocks make up a list of memory blocks, and the MemoryPool Pblock is the head of the list. For each block of memory, the block of memory immediately following it can be accessed through the Pnext member of the memoryblock structure of its head.

(2) Each memory block consists of two parts, a memoryblock structure and multiple memory allocation units. These memory allocation units are fixed in size (represented by MemoryPool's nunitsize), and the Memoryblock struct does not maintain information about the units that have been allocated, instead, it maintains only the information that is not allocated to the free allocation unit. It has two members that are more important: Nfree and Nfirst. Nfree records how many free-allocation units are in this memory block, while Nfirst records the number of the next unit that can be allocated. The first two bytes of each free allocation unit (that is, a ushort value) records the number of the next free-allocation unit immediately following it, so that all the free-allocation units in a memoryblock are linked by taking advantage of the first two bytes of each free-allocation unit.

(3) When a new memory request arrives, MemoryPool traverses the memoryblock linked list through pblock until it finds a block of memory where a memoryblock is located. There are also free distribution units (by detecting whether the Nfree member of the MEMORYBLOCK structure is greater than 0). If such a block of memory is found, the Nfirst value of its memoryblock is obtained (this is the number of the 1th free unit in the memory block that can be allocated). It then navigates to the starting position of the free allocation unit based on this number (since all allocation units are fixed in size, so the starting position of each allocation unit can be offset by the number allocation unit size), which is the starting address of the memory used to satisfy the memory request. Before returning this address, however, the first two-byte value (the number of the next free-allocation unit after which the two-byte value is recorded) must be assigned to the Nfirst member of the Memoryblock in this memory block. The next request will be satisfied with the memory unit of this number, and the memoryblock of this memory block will be decremented by 1, then the starting position of the memory unit that was just located to the caller is returned as the return address of the memory request.

(4) If a free memory allocation unit is not found from the existing memory block (this happens when the 1th memory is requested and all memory allocation units in all existing memory blocks have been allocated), MemoryPool will request a block of memory from the process heap (this memory block consists of a memoryblock struct, and a plurality of memory allocation units adjacent to it, assuming that the number of memory allocation units is N, n can take the value of the MemoryPool in the Ninitsize or ngrowsize), after the application is completed, will not immediately assign one of the allocation unit, but need to first initialize the memory block. The initialization action includes setting the size of the Memoryblock nsize for all memory allocation units (note that does not include the size of the Memoryblock struct), Nfree for n-1 (note that this is n-1 instead of N, Because this new memory block is to satisfy a new memory request, will immediately allocate a free storage unit out, if set to n-1, the allocation of a free storage unit no longer to Decrement N 1), Nfirst 1 (already know Nfirst for the next can allocate the number of free storage units. The reason for 1 is the same as Nfree for N-1, which immediately assigns the free allocation unit numbered 0. Now set to 1, after which you do not have to modify the value of Nfirst, Memoryblock's construction needs to do something more important, linking all the free allocation units after the allocation unit numbered 0. As mentioned earlier, the first two bytes of each free allocation unit are used to store the number of the next free allocation unit. Also, because each allocation unit is fixed in size, it can be positioned by the product of its number and cell size (MemoryPool's nunitsize member) as an offset value. Now the only question is which address to start with? The answer is Memoryblock's adata[1] member begins. Because Adata[1] is actually part of the Memoryblock struct (the last byte of the Memoryblock struct), in essence, the last byte of the Memoryblock struct is also used as part of the assigned allocation unit. Because the entire memory block consists of a memoryblock struct and an entire number of allocation units, this means that the last byte of the memory block is wasted, and this byte is identified in Figure 6-2 with a small black background in the last part of two memory. Once you have identified the starting position of the allocation unit, it is easy to link the free-allocation unit. That is, starting from the Adata position, each nunitsize size takes its first two bytes, and the number of the free allocation unit after it is recorded. Since all allocation units are free at first, this number is the own number plus 1, which is immediately followed by the position.The number of the cell. After initialization, the starting address of the 1th allocation unit of this memory block is returned, and the address is known to be adata.

(5) When a unit is allocated because the delete needs to be reclaimed, the cell is not returned to the process heap, but is returned to MemoryPool. When returned, MemoryPool is able to know the starting address of the cell. At this point, MemoryPool begins to traverse the list of memory blocks it maintains to determine whether the starting address of the cell falls within the address range of a block of memory. If it is not within the range of all memory addresses, the recovered unit does not belong to this memorypool, and if it is within the address range of a memory block, it adds the newly-recovered allocation unit to the head of the free-allocation unit list maintained by the Memoryblock of the memory block, The Nfree value is incremented by 1 at the same time. After recycling, taking into account the efficient use of resources and the performance of subsequent operations, the memory pool operation will continue to judge: if all the allocation units of this memory block are free, then the memory block will be removed from the MemoryPool and returned as a whole to the process heap, if there are non-free allocation units in the memory block, This block of memory cannot be returned to the process heap at this time. But since an allocation unit has just been returned to this memory block, the memory block has a free allocation unit for the next assignment, so it is moved to the head of the memory block that MemoryPool maintains. So the next time a memory request arrives, the memory block is found for the 1th time when MemoryPool iterates through its list of memory blocks to find the free allocation unit. Because this memory block does have a free allocation unit, this reduces the number of memorypool traversal times.

In summary, each memory pool (memorypool) maintains a list of memory blocks (single-linked lists), each of which consists of a block structure (memoryblock) and multiple allocation units that maintain the memory block information. The block structure Memoryblock further maintains a "linked list" of all the free allocation units of the memory blocks. This list is not linked by a pointer to the next free allocation unit, but is linked by the number of the next free allocation unit, which is stored in the first two bytes of the free allocation unit. In addition, the starting position of the 1th free allocation unit is not the 1th address position behind the memoryblock struct, but the last byte of the Memoryblock structure "inside" adata (or possibly not the last, because of the problem of byte alignment), That is, the allocation unit is actually one of the wrong people ahead. And because the space behind the memoryblock structure is just an integer multiple of the allocation unit, which in turn is misplaced, the last byte of the memory block is not actually exploited. One reason to do this is to consider porting issues for different platforms, because the alignment of different platforms may vary. That is, when you request Memoryblock size memory, you may return memory that is larger than the sum of all its member sizes. The last few bytes are meant to be "padded," making the Adata the starting position of the 1th allocation unit, so that it works on various platforms with different alignment options.

6.2.3 Detail Analysis

With this overall impression, this section examines the implementation details.

(1) The structure of the MemoryPool is as follows:

Memorypool::memorypool (USHORT _nunitsize,                            USHORT _ninitsize, USHORT _ngrowsize) {    pblock      = NULL;            ①    ninitsize   = _ninitsize;       ②    ngrowsize   = _ngrowsize;       ③    if (_nunitsize > 4)        nunitsize = (_nunitsize + (mempool_alignment-1)) & ~ (mempool_alignment-1); ④
      
       else if (_nunitsize <= 2)        nunitsize = 2;              ⑤    Else        nunitsize = 4;}
      

As you can see from ①, when MemoryPool was created, it did not immediately create a memory block that was actually used to satisfy the memory request, that is, when the memory block list was initially empty.

② and ③ set the number of allocation units included in the memory block created for the 1th time, and the number of allocation units that are subsequently created by the memory blocks, which are specified by a parameter when MemoryPool is created, and then remain unchanged for the MemoryPool object life cycle.

The following code is used to set the Nunitsize, which refers to the incoming _nunitsize parameter. But there are two factors to consider. As mentioned earlier, each allocation unit is in a free State, and its first two bytes are used to hold "the number of its next free allocation unit". That is, each allocation unit "at least" has "two bytes", which is the reason for assigning values at ⑤. The ④ is a multiple of the smallest Mempool_ alignment (provided that mempool_alignment is a multiple of 2) that is larger than the size of 4 bytes _nunitsize upward "rounding to" greater than _nunitsize. If _nunitsize is 11 o'clock, Mempool_alignment is 8,nunitsize for 16;mempool_alignment 4,nunitsize for 12;mempool_alignment for 2, Nunitsize is 12, and so on.

(2) When a memory request is made to MemoryPool:

void* Memorypool::alloc () {    if (!pblock)           ①    {...    }    memoryblock* pmyblock = pblock;    while (Pmyblock &&!pmyblock->nfree) ②        pmyblock = pmyblock->pnext;    if (pmyblock)         ③    {        char* pfree = pmyblock->adata+ (pmyblock->nfirst*nunitsize);        Pmyblock->nfirst = * ((ushort*) pfree);        pmyblock->nfree--;        Return (void*) pfree;    }    else                    ④    {        if (!ngrowsize)            return null;pmyblock = new (Ngrowsize, nunitsize) Fixedmemblock ( Ngrowsize, nunitsize);        if (!pmyblock)            return NULL;        Pmyblock->pnext = pblock;        pblock = Pmyblock;        Return (void*) (pmyblock->adata);}    }

MemoryPool the steps to satisfy the memory request consist of four steps.

① first determine if the memory pool current memory block list is empty, if it is empty, it means that this is the 1th time the memory request. At this point, request a block of memory from the process heap with an allocation unit number of ninitsize, and initialize the memory block (primarily initializing the memoryblock struct member, and creating the initial free allocation unit list, which will be analyzed in detail below). If the memory block request succeeds, and the initialization is complete, the 1th allocation unit is returned to the calling function. The 1th allocation unit takes the last byte in the body of the memoryblock structure as the starting address.

The ② function is to traverse the memory block list when there is already a block of memory in the memory pool (that is, the memory block list is not empty), looking for a block of memory that also has a "free allocation unit".

③ Checks if you find a block of memory that also has a free allocation unit, then "position" to the free-allocation unit that the memory block can now use. "Positioning" takes the last byte position in the body of the memoryblock structure Adata as the starting position, with the nunitsize of the MemoryPool as the step size. Once found, it is necessary to modify the Nfree information of the Memoryblock (the remaining free allocation unit is less than the original), and to modify the free storage unit list information for this memory block. In the memory block found, the Pmyblock->nfirst is the table header of the free storage cell list in the memory block, and the number of the next free storage unit is stored in the pmyblock-> The first two bytes of the free storage unit indicated by the Nfirst (i.e. the free storage unit that was just positioned). By the position you just positioned, take the first two bytes of the value, assign to Pmyblock->nfirst, this is the memory block of the free storage unit linked list of the new header, that is, the next allocation of the free allocation unit number (if Nfree is greater than 0). After modifying the maintenance information, you can return the address of the free allocation unit you just located to the call function for this application. Note that since this allocation unit has already been allocated, and the memory block does not need to maintain the allocated allocation unit, the first two bytes of the allocation unit information is useless. On the other hand, this free allocation unit returns to the calling function, and the memory pool is not known or known when the function is called to dispose of the memory. When this allocation unit is returned to the calling function, its contents are meaningless to the calling function. As a result, it is almost certain that the calling function overwrites its original contents with the memory of the unit, that is, the contents of the first two bytes will be erased. Therefore, each storage unit does not introduce redundant maintenance information because it needs a link, but instead uses the first two bytes in the unit directly, and when it is allocated, the first two bytes can also be used by the calling function. While in the Free State, it is used to store maintenance information, that is, the next free allocation unit number, which is a good example of efficient use of memory.

④ indicates that a memory block with a free allocation unit is not found while traversing the ②, and a memory block needs to be re-applied to the process heap. Because it is not the first time that a memory block is applied, the requested memory block contains the number of allocation units ngrowsize, not ninitsize. In the same way as ①, the new application initializes the memory block, inserts the memory block into the head of the MemoryPool memory block list, and returns the 1th allocation unit of this memory block to the calling function. The reason this new block of memory is inserted into the head of the memory block list is that the memory block also has a lot of free allocation units available for allocation (unless ngrowsize equals 1, this should not be possible.) Because the memory pool means to request a chunk of memory from the process heap at once for subsequent multiple requests, placing it on the head can reduce the time it takes to traverse the memory block at ② the next time the memory request is received.

You can use the memorypool of Figure 6-2 to demonstrate the memorypool::alloc process. Figure 6-3 is the internal state of a memorypool at some point.


Figure 6-3 Internal state of MemoryPool at some point

Because MemoryPool's memory block list is not empty, it traverses its memory block list. And because there is a free allocation unit in the 1th memory block, it is allocated from the 1th memory block. Check Nfirst, whose value is M, at which point pblock->adata+ (pblock->nfirst*nunitsize) navigates to the starting position of the free-allocation unit numbered m (denoted by pfree). You need to modify the maintenance information for this block of memory before returning to Pfree. First, the Nfree is decremented by 1, and then the first two bytes that begin at Pfree are obtained (it should be stated that the value here is K adata. This is not actually a byte. Instead, the value of a ushort, which is made up of adata and the other byte immediately following it, cannot be misunderstood. Found as K, then modified Pblock Nfirst is K. Then, return to Pfree. At this point the structure of MemoryPool is shown in 6-4.


Figure 6-4 Structure of MemoryPool

As you can see, the original 1th allocated unit (m number) has been shown as the assigned state. And Pblock's Nfirst has pointed to the original m unit of the next free allocation unit number, namely K.

(3) MemoryPool when recovering memory:

void Memorypool::free (void* pfree) {    ...    memoryblock* pmyblock = pblock;    while ((ULONG) Pmyblock->adata > (ULONG) pfree) | |         (ULONG) Pfree >= ((ULONG) Pmyblock->adata + pmyblock->nsize)) ①    {         ...    }    pmyblock->nfree++;                     ②    * ((ushort*) pfree) = pmyblock->nfirst;  ③    Pmyblock->nfirst = (USHORT) ((ULONG) pfree-(ULONG) (Pblock->adata))/nunitsize); ④    if (pmyblock- >nfree*nunitsize = = pmyblock->nsize) ⑤    {        ...    }    else    {        ...    }}

As mentioned earlier, when the allocation unit is reclaimed, the entire block of memory may be returned to the process heap, or the memory block to which the reclaimed allocation unit belongs can be moved to the head of the memory pool's list of memory blocks. Both operations need to modify the list structure. You need to know the memory block in the previous position in the linked list.

① iterates over the memory block list of the memory pool, determines which memory block the allocation unit (PFREE) falls within the pointer range of, by comparing the pointer values.

Running to ②, Pmyblock finds the block of memory that contains the allocation unit to be reclaimed by pfree (of course, you should also check the case where Pmyblock is null, that is, Pfree is not part of this memory pool and therefore cannot be returned to this memory pool, which the reader can add by itself). At this point the Pmyblock nfree is incremented by 1, indicating that the free allocation unit of this memory block is one more.

The information in the ③ to modify the free-allocation unit list of the memory block, which points to the first two-byte value of the allocation unit to be reclaimed to the number of the original assignable free-allocation unit of that memory block.

④ changes the Nfirst value of the Pmyblock to the number of the allocation unit to be reclaimed, and its number is calculated by calculating the difference between the starting position of the cell relative to the Adata position pmyblock, and then dividing by the step size (nunitsize).

Essentially, the ③ and ④ two steps are to "really reclaim" the allocation unit to be recycled. It is important to note that these two steps actually make this collection unit the next assignable free allocation unit of this block of memory, which is placed in the head of the free distribution Unit list. Note that the memory address has not changed. In fact, the memory address of an allocation unit does not change, either after it is allocated or in a free state. The change is only its state (allocated/free), and its position in the free-distribution unit linked list when it is in a free state.

⑤ check that when the collection is complete, all the cells that contain the memory block for this collection unit are free, and the memory is in the head of the memory block list. If so, the entire memory block is returned to the process heap, while the memory block list structure is modified.

Note that here, when judging whether all cells of a memory block are free, do not traverse all of their cells, but rather determine whether nfree times Nunitsize equals nsize. Nsize is the size of all allocation units in a memory block, not the size of the head memoryblock structure. The intent here is to quickly check that all allocation units in a block of memory are in Free State. It is not necessary to traverse and calculate the number of allocation units of all free states simply by combining Nfree and nunitsize to calculate the conclusion.

Also note that it is not possible to compare the size of nfree with ninitsize or ngrowsize to determine that all allocation units in a memory block are free, because the 1th allocated memory block (the number of allocation units is ninitsize) may be moved to the back of the linked list. Even after moving to the back of a linked list, it is returned to the process heap because all of its cells are free State at a certain time. That is, when the allocation unit is recycled, it is not possible to determine whether the number of allocation units in a memory block is ninitsize or ngrowsize, and it is not possible to compare the size of Nfree and ninitsize or ngrowsize to determine if all allocation units of a block of memory are free states.

As an example of the memory pool State allocated above, assume that the last cell in the 2nd memory block needs to be reclaimed (allocated, assuming it is numbered as m,pfree pointer to it), 6-5.

It is not difficult to find, then the value of the Nfirst from the original 0 into M. That is, the next allocated unit of this memory block is the M-numbered unit, not the 0 numbered unit (the first to be allocated is the newest recovered unit, from this point of view, this process is similar to the principle of the stack, that is, advanced post-out. But the "in" here means "recycling" and "out" means "assigning". Accordingly, M's "Next free unit" is labeled 0, which is the original "next unit to be allocated" in memory block, which also indicates that the recently recovered allocation unit is inserted into the head of the "Free Distribution Unit list" of memory blocks. Of course, nfree increments by 1.


Figure 6-5 Memory pool status after allocation

Before processing to ⑥, its status 6-6 is shown.


Figure 6-6 Memory pool status before processing to ⑥

It is important to note that although the Pfree is "recycled", the Pfree still points to the M-numbered unit, which is overwritten with the first two bytes during the recycling process, but the rest of the content does not change. And from the memory usage point of view of the whole process, the status of this m-numbered cell is still "valid". Because the "recycle" here is simply recycled to the memory pool and not recycled to the process heap, the program can still access the unit through Pfree. However, this is a very dangerous operation, because first the unit in the recycling process the first two bytes are overwritten, and the unit may soon be re-allocated by the memory pool. Therefore, after recycling through pfree means that access to this unit is wrong, the read operation will read the wrong data, the write operation may break the data elsewhere in the program, so you need to be extra careful.

Next, you need to determine the internal usage of the memory block and its position in the memory block list. If the ellipsis "..." in the memory block also represents the other part of the allocated unit, that is, nfree times nunitsize is not equal to nsize. Because this block of memory is not in the list header, it is also necessary to move it to the head of the list, as shown in 6-7.


Figure 6-7 Memoryblock movement due to recycling

If the ellipsis "..." in the memory block is all other parts of the expression, all are free allocation units, that is, nfree times nunitsize equals nsize. Because this memory block is not in the list header, this memory block needs to be recycled to the process heap at this time, as shown in structure 6-8 of the memory pool after recycling.


Figure 6-8 Structure of the memory pool after recycling

A block of memory is initialized after the application, primarily to establish the initial free-distribution unit list, with its detailed code:

Memoryblock::memoryblock (USHORT ntypes, USHORT nunitsize): NSize  (Ntypes * nunitsize),  nfree  (nTypes-1),                     ④  Nfirst (1),                              ⑤  pnext  (0) {char * pData = AData;                  ①for (USHORT i = 1; i < ntypes; i++) ②{*reinterpret_cast<ushort*> (pData) = I;③pdata + = Nunitsize;}}

As can be seen here, the initial value of the pdata at ① is Adata, which is the 0 numbering unit. But the ② in the loop i is starting from 1, and then in the loop inside the ③ place pdata the first two bytes value to I. That is, the first two bytes value of unit No. 0 is 1, and the first two bytes value of unit 1th is 2, until the first two bytes value (nTypes-1) of the (nTypes-2) unit. This means that when the memory block is initially set, its free-allocation unit list is starting from number No. 0. In series, go to the bottom 2nd cell and point to the last cell.

It is also important to note that in its initialization list, Nfree is initialized to NTypes-1 (instead of ntypes), Nfirst is initialized to 1 (instead of 0). This is because the 1th unit, the 0 numbering unit, is allocated immediately after the construction is complete. Also note that the last unit initially did not set the first two bytes value, because the unit initially in this memory block does not have the next free allocation unit. However, as can be seen from the above example, when the last unit is allocated and reclaimed, its first two bytes are set.

Figure 6-9 shows the state after the initialization of a block of memory.


Figure 6-9 State after initialization of a memory block

When a memory pool is refactored, all memory blocks of the memory pool need to be returned to the process heap:

Memorypool::~memorypool () {    memoryblock* pmyblock = pblock;    while (Pmyblock)    {        ...    }}

6.2.4 How to use

After analyzing the internal principles of the memory pool, this section explains how to use it. As you can see from the above analysis, the memory pool has two external interface functions, namely alloc and free. Alloc returns the allocated unit (fixed-size memory) that is requested, and free reclaims the memory of the allocated unit represented by the incoming pointer to the memory pool. The assigned information is specified by the constructor of the MemoryPool, including the allocation unit size, the number of allocation units included in the memory pool for the 1th time that the memory pools are requested, and the number of allocation units included in memory blocks for subsequent requests from the memory pool.

In summary, when you need to improve the application/recycling efficiency of certain key class objects, consider opening up the space required for all the generated objects of that class from one of these memory pools. When you destroy an object, you only need to return it to that memory pool. "All objects of a class are allocated in the same memory pool object" The natural design method is to declare a static memory pool object for such a class, and in order for all of its objects to open up memory from the memory pool, instead of the default from the process heap, you need to overload a new operator for that class. As a result, recycling is also a memory pool, not the default heap for the process, and a delete operator is also required for overloading. The Alloc function of the memory pool in the new operator satisfies the memory request of all such objects, while destroying an object can be done by invoking the free memory pool in the delete operator.

6.2.5 Performance Comparison

To test the effect of using a memory pool, a small test program can be used to discover that the memory pooling mechanism is time-consuming for 297 Ms. Instead of using the memory pool mechanism, it takes 625 ms to increase the speed by 52.48%. The reason for the increase in speed can be attributed to a few points, one, except that occasional memory requests and destruction will result in allocating and destroying memory blocks from the process heap, and the vast majority of memory requests and destruction are made by the memory pool in the memory block that has been requested, rather than directly dealing with the process heap, which is time-consuming to deal with Second, this is a memory pool for single-threaded environments, and you can see that there is no threading protection in the Alloc and free operations of the memory pool. Therefore, if Class A is used in this memory pool, all Class A object creation and destruction must occur in the same thread. However, if Class A is used in a memory pool and Class B is also used in a memory pool, the use of Class A thread can not have to be the same thread as the usage thread of Class B.

In addition, it has been discussed in the 1th chapter because memory pooling techniques allow objects of the same type to be distributed across adjacent memory regions, and the program often iterates over objects of the same type. Therefore, there should be fewer pages in the process, but this can only be verified in a real complex application environment.

6.3 Summary of this chapter

The application and release of memory has a great impact on the overall performance of an application, and even becomes a bottleneck for an application in many cases. The way to eliminate the bottlenecks caused by memory requests and releases is often to provide a suitable memory pool for the actual memory usage. A memory pool can improve performance primarily because it can take advantage of some of the "features" in the application's actual memory usage scenario. For example, some memory requests and releases must occur in one thread, and some types of objects are generated and destroyed much more frequently than other types of objects in the application, and so on. For these features, you can provide a customized memory pool for these special memory usage scenarios. This eliminates the default memory mechanism provided by the system, which increases the overall performance of the application for unnecessary operations in the actual scenario.

The principle and implementation of memory pool technology

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.