In C++ you have the choice between various memory allocation strategies. In addition to the frequently used dynamic memory allocation, you have the stack allocation. But you can preallocate the memory at the start time of your program. This can be a fixed-sized block or one or more memory pools. Each of these strategies has its pros and cons. Which one, I will answer in this post.
In the last post, I presented four typical strategies for managing memory. With this post, I will go one step further by presenting the differences in a table. Before I do this, I will define the criteria which are the base for my comparison.
Internal and external fragmentation are two big issues of dynamic memory allocation and therefore for memory pools too.
Internal fragmentation A object needs not the whole memory area that was reserved for it.
External fragmentation The unused memory between the objects is too small to be used for new objects.
Interestingly, a system that is optimized for internal fragmentations has often issues with external fragmentation. This holds also the other way around. The internal fragmentation is less the better the memory area fits the object. The consequence is that the object sizes are quite irregular and you can not efficiently use the available memory. If you create equally sized memory areas for each object, the internal memory fragmentation will increase because you typically put the objects in oversized memory blocks.
One solution to the problem is to adjust the memory blocks to the object sizes and to move the memory blocks if needed. Therefore, the system is optimized for internal and external fragmentation. But this stays in contradiction to the temporal predictability of the memory allocation because it requires extra effort at runtime.
The big advantage of dynamic memory allocation is that the sheer amount of memory seems to be unlimited. This observation does not hold for a fixed memory block allocated at the start time of the program or for the stack.
If I ignore smart pointers like std::unique_ptr or std::shared_ptr or the Boehm garbage collector, you have to explicitly release the memory in case of dynamic memory allocation or pool allocation. This will not hold for the stack. In the case of stack allocation, the C++ runtime automatically releases the memory; in the case of the allocation of a fixed-sized block, the memory will usually not be released.
You will get memory leaks if you don't release the memory.
Stack allocation is per se free of memory leaks. This will not hold true for the other three memory allocations. In particular, memory leaks are a big concern of dynamic memory allocation and memory pools.
Predictability is a big issue in such kind of applications that deals with hard real-time requirements. That means, in particular, there are time windows in which an operation must be performed. This kind of guarantee can not be given by dynamic memory allocation. For example, the memory may be fragmented. Therefore, often dynamic memory allocation is no option in an embedded system driven by real-time requirements. The restriction holds not for the three other ways to allocate memory. They have deterministic temporal behaviour.
Ease of use
From the user perspective, each of the four presented memory allocations has its pros and cons. Therefore, the programmer has in the case of dynamic memory allocation and memory pool the duty to manage its memory. He has to explicitly call delete or free. This is contrary to a fixed-sized memory block or the stack. But there are other cons for both.
If you allocated a fixed-sized block, you can not perform an arbitrary number of allocations. The same holds for the stack. You can not dynamically create objects on the stack and the lifetime of your object is bound to the lifetime of the stack.
The variability is of course the benefit of dynamic memory allocation. This is true - with small restrictions - for the memory pool, if you have memory pools of various sizes or the memory pool is sufficiently big enough. Therefore, the program can react to the extraordinary memory request. This benefit will not hold for the stack or the allocation of the fixed-sized memory block. Both memory allocations require predictable memory requirements.
The big picture
At the end, the table provides an overview of all criteria.
A want to say a few words about the table. I sometimes choose the answer Yes/No in the case of the memory pool. The answer depends on the fact, how many memory pools are available. I want to emphasize one point. The memory pool offers the best from two worlds. On one hand, you have - like in the case of dynamic memory allocation - dynamic behaviour; on the other hand, you have fully deterministic temporal behaviour - like a stack of a fixed-sized memory block - for each memory allocation. The strength is reflected in the table.
Jonathan Müller, author of the English blog foonathan::blog() and in the particular author of the memory library ("STL compatible C++ memory allocator library using a new RawAllocator concept that is similar to an Allocator but easier to use and write.") will write in the next post about his memory pool allocators. I'm very happy to pronounce it. If I learned at least one lesson from these posts about memory allocation with C++ then that it is a quite challenging endeavour.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Get your e-book. Support my blog.