Memory Management Technology (II), memory pool, and memory management

Source: Internet
Author: User

Memory Management Technology (II), memory pool, and memory management

Well, this article describes the available multi-thread memory pool.

 

Zero or previous eggs: Do not reload Global new

Maybe it's a very unpleasant experience, so there will be such an "Understanding ". Anyway, it is probably: Even if you are smart enough, do not be smart; in this case, do not reload the global new, no matter what purpose and IQ you have. Because:

class XXX{public:    XXX* createInstance();};

This is an asymmetric interface: It only tells us how to create a [heap] object, but release it ??! It is helpless. It can only be assumed that it uses the Global default delete to delete (there are no other options). In this case, for some purpose, the global delete is overloaded (perhaps for monitoring, optimization, or ...):

Void operator delete (void * addr) {... // something happened... std: free (addr );}

This is a natural practice; however, it will collapse in the future or in the present day; its name is: heap error. That is, the cause of the crash is also very simple: Who in the Code does not use std: malloc to allocate memory-for example, the previous [XXX ], no one knows whether it is allocated to the stack: the default system heap or the debug heap in VS-debug (this is a pitfall ).

Of course, we can be careful enough. For example, we can carefully examine the allocation methods of each object and take special care for new objects. Alternatively, you can:

Void operator delete (void * addr) {... // something happened again...: operator delete (add );}

We finally used the original global release method. Well, this is a safe method. Of course, there is only one custom part: monitoring. You cannot optimize it by using a custom memory allocation method (such as the memory pool to be discussed. Of course, it would be fine if there is no [XXX] to stir up the problem. However, once the [Global] operation is modified, you must provide extremely robust support and guarantee.

Finally, because it is a global and implicit call, you cannot fully control it. When will this operation be called, when is it not called (when your code is called as a lib/dll library, but the new overload is not exported); if there is a smart person, so when you need to mix the code (such as lib/dll), you will feel that the whole world is not good .......

Of course, this is my opinion. If you insist on using it, we recommend that you use macros to call non-Global version equivalents.

 

1. What is a memory pool?

Well, there is the following code:

Struct BlockNode {BlockNode * next;}; // create a bunch of BlockNodeBlockNode * allocate (size_t index, size_t count); // The external memory allocation interface void * alloc (size_t size) {size_t index = (size-1)/8 + 1; BlockNode * data = freeList [index]; if (data) {freeList [index] = data-> next; return data;} else {freeList [index] = allocate (index,/* a reasonable large number */); return alloc (size );}} // The void dealloc (void * addr, size_t size) interface for external memory release {// The opposite operation with alloc (I am lazy )}

The above is all the details of the memory pool itself; as for allocate and dealloc, it is not hard to imagine.

 

2. What is a multi-thread memory pool?

Straightforward Translation: memory pools that support multi-thread security operations. Of course, we cannot achieve security through locking; otherwise, we will only do worse (it will be slower than the system... possibly ).

Therefore, here we need to use one of the technologies to be discussed in the next article: TLS (local thread storage ). Yes, we will create such a memory pool in every thread; in this way, we can naturally obtain thread security during memory allocation without locks. So what about release?

// Occurs in the thread Avoid * addr = alloc (23 );...... // there are many things happening here ...... dealloc (addr, 23); // where is this? Is it thread?

If your code supports multiple threads, when the memory is released, it will not necessarily be the thread at the original allocation! Of course, we can mark each segment of memory to indicate which thread it was born in. Well, this is a good attempt to fail. First, the complexity of the next step will kill you (how to return to the allocation thread when different threads are released). Second, what if the allocation thread is dead ??? (I believe there will always be a way for you to be smart ....)

Therefore, we need a reasonable and effective model: Memory Pool swap between threads-using a global shared memory pool, and then the memory pool inside each thread, initiate a request for allocation and release. In this way, we will not worry about the above problems; we can use this global pool to complete cross-thread memory operations.

Of course, there is no doubt that the global pool needs to be locked. To reduce the lock consumption, we can shorten the Access Frequency of the internal pool of the thread, for example, the distribution/release frequency of the internal pool and the Access Frequency of the global pool. The ratio is 10000: 1, or higher. In this way, the consumption of the last lock is almost completely absent through the evenly distributed model (even if the consumption is 1 ms, only 0.1us is available after the evenly distributed model ).

Therefore, the challenge is: how to maintain the share ratio? (Because, in malformed allocation, it will become or even lower)

 

3. We need performance!

If there is no better performance, then we still need to make hair ?? Therefore, the biggest purpose here is to maintain the proportion of our predefined average share. Or control the access frequency from the internal thread pool to the global pool.

For the allocation Request Policy to the global pool, we can use an application value that is large enough: 100000, or 10 MB.

BlockNode * ThreadMemoryPool: allocate (size_t index, size_t size) {// we apply for a BlockNode * result = globalPool. allocate (100000*8 * (index + 1); return result ;}

So when will it be released? For example, if thread A has applied for 10 MB of memory, how can it be released? Release what? How many are released? This has always been a blind spot (for me, it has not been completely solved for a few years ). Of course, you may have various solutions right away.

Why should we release it ??? Because other threads have no memory available, and you, thread A is holding TB of memory.

 

4. We need to balance...

We cannot tolerate it. Any thread holds more than 10 MB of available memory !!! Therefore, the following solutions are available:

Void ThreadMemoryPool: dealloc (void * addr, size_t size ){..... // We should not care about the release process... if (listSize [index]> 1024*1024*10) {globalPool. deallocte (freeList [index], listsize [index]);}

During the release operation of the internal thread pool, check whether the current pool has more than 10 MB of memory. If yes, we will abort it! At this time, there will be a malformed distribution:

// Thread A requests 10 mbthreadmemorypool from the global pool. allocate (index, 10 MB); // consumes one internal unit (currently holding 10 MB-one unit) threadMemoryPool. alloc (...);... // thread A releases A unit (currently holding 10 MB) of threadMemoryPool. dealloc (...);... // thread A releases A threadMemoryPool (currently 10 MB + 1 unit> 10 MB. dealloc (...);

After a total of three allocation/release operations, the requested memory is returned to the global pool. At this time, the average share ratio is (three internal operations and two global operations: application + release ), second, any operation of the global pool itself consumes a huge amount (for example, where the 10 MB memory comes from, from the system), then the actual proportion will change to or even lower.

Of course, we can stagger the global operation thresholds for allocation and release, such as 1 MB allocation and 10 MB release. In this way, we can leave 10-1 = 9 MB without the above situation. (Of course, the opposite is definitely not true: allocate 10 MB, Release 1 MB, as you can imagine .)

 

5. What else do we need?

If the allocation value is not the same as the release threshold, we may never be able to reclaim the memory that is smaller than the release threshold but greater than or equal to the allocation value. In the most common case, all allocation and release operations of thread A are performed locally.

// Thread Afor (I = 0: 10000) {data [I] = threadMemoryPool. alloc (size );}....... // thread Afor (I = 0: 10000) {threadMemoryPool. dealloc (data [I], size);} // No

There is no operation after this, so until the thread dies; we cannot recycle this available memory!

Therefore, we need to allocate a value, which is reasonable enough, and the release threshold must be small enough to maintain the average share ratio. Of course, we can do it! We only need to completely isolate the [allocated] memory value held by the current internal pool and the [released] memory value.

Void ThreadMemoryPool: dealloc (void * addr, size_t size ){..... // We still don't care about the release process... deadSize [index] + = (index + 1) * 8; // use an additional dead memory value if (deadSize [index]> 1024*1024*10) {globalPool. deallocate (freeList [index], listsize [index]) ;}}

This is so simple that it has plagued me for so long... at this moment, we can freely operate on the allocation value and release threshold to maintain a reasonable share ratio we think.

 

6. What else do we need ??

It may be noted that we maintain the illusion that our memory pool can be recycled.

1. We cannot release memory that we may not use to the system. (This is a memory leak for some code and the system itself that didn't use our memory pool !)

2. You may have noticed this [index]. Each internal pool maintains several node chains of different sizes. We cannot reuse these chains of different sizes. (This is a leak in the memory pool)

Yes, we may be creating the largest memory leak; it is also caused by an Inevitable Way (we need to allocate memory with higher performance ). Theoretically, this is inevitable. The only thing we can do is to avoid the above situation and evolve into the worst situation.

Therefore, we may need a more refined model. We need to transform the [global pool ]!! It can support memory sorting to a certain extent.

Therefore, we need to make two improvements:

1. We need to save the address (to release) of each large memory allocated from the system ).

2. We need an algorithm that can sort out all the memory we don't need and restore it to the State allocated from the system (full block of memory ).

At this time, we can achieve the previous two goals: release to the system, reuse between node chains (by releasing the system, and then getting it from the system again ). Similar functions are implemented in my personal memory pool. Therefore, I believe it is not difficult to achieve this.

 

7. What else do we need ???

The important thing is three times ......

Back to the initial question: why do we need a memory pool? We need performance. So what else is worth our attention; to achieve higher performance?

Yes, there are many more! In the previous release to the global pool, there was a key detail that did not mention: Where is the memory we released? How can I reuse it? There are two solutions:

1. Like the internal thread pool, maintain a chain and add the released part to the chain (requires O (n). During allocation, get from the chain (also requires O (n )).

2. Package the node chain and save it as a separate chain. The chain is directly returned when it is allocated to the internal pool. (Only O (1 ))

Intuitively, we will choose the second solution (even though we may only think of the first one at first ). However, once the second type is used, we cannot control the memory size applied by each thread: we can only return one available node chain, it cannot guarantee whether it is the expected size. (At most, we can ensure that the return value is greater than the expected value, but not the same as the expected value; otherwise, the complexity of O (1) will be damaged)

Well, we have lost a small part of our ability to control the shares. However, as long as we are careful enough to schedule the release threshold, there will be no malformed situations.

Second, we can be more careful when allocating: instead of directly applying for 1 MB (only a small part may be used), we can apply for a growth strategy (for example: 100, 200, 400 ....). We can do a lot of things we want to do on the premise that we can maintain an even share ratio.

To sum up, we need a large enough allocation value and a release threshold to maintain a reasonable evenly shared ratio, and we want to keep a small enough memory to avoid any possible memory leakage. Well, this is exactly what we are pursuing. There is only one remaining one: trade-offs.

 

PS: After the memory pool is used, the memory out-of-bounds occurs. The consequence will be disastrous for debugging.

Related Article

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.