A custom allocator can improve performance, increase memory utilization efficiency, and address the issue of frequent, small memory allocations.
Antecedent
Recently, I’ve been working on the development of network data packets, requiring frequent allocation and release of small blocks of memory. Initially, I considered using a memory pool, reviewing several existing ones and discovering this:
https://github.com/cacay/MemoryPool
When looking at the interface, I was quite puzzled by how the memory pool’s implementation was a bit strange. The MemoryPool
implementation logic involves allocating fixed-size memory blocks. Having reviewed Boost’s memory pool interface, it provides a template that is instantiated when used. Fortunately, this library already had an article describing it, mentioning the concept of an ‘allocator’.
#### [wiki](https://zh.wikipedia.org/wiki/%E5%88%86%E9%85%8D%E5%99%A8_(C%2B%2B))
In C++ programming, an allocator is a key component of the C++ standard library. The C++ standard library defines various data structures commonly referred to as "containers" (such as linked lists, sets, etc.). A common feature of these containers is that their size can be changed at runtime; therefore, dynamic memory allocation becomes necessary to achieve this. The allocator is used to handle memory allocation and deallocation requests made by the containers. In other words, the allocator encapsulates the low-level details of memory management for Standard Template Library (STL) containers. By default, the C++ standard library uses its built-in generic allocator, but programmers can customize allocators to replace it as needed.
The allocator was originally invented by Alexander Stepanov as part of the C++ Standard Template Library (STL), with the intention of creating a mechanism that would "make the library more flexible and allow independent use of low-level data models," and enable programmers to utilize custom pointer and reference types within the library; however, when the STL was incorporated into the C++ standard, the C++ standards committee realized that complete abstraction of the data model would result in unacceptable performance penalties. To compromise, restrictions on the allocator were made more stringent, and compared to Stepanov's original vision, the degree to which the current standard describes allocators is greatly limited.
Although customization of the allocator is somewhat restricted, it is still needed in many cases, typically for encapsulating access to different types of memory spaces (such as shared memory and reclaimed memory), or for improving performance when using a memory pool for memory allocation. In addition, from the perspective of memory usage and execution time, in programs that frequently perform small amounts of memory allocation, introducing a dedicated allocator can also yield benefits.
- Data Mining
- Deep Learning
- Neural Network
#### [Usage Requirements](https://zh.wikipedia.org/wiki/%E5%88%86%E9%85%8D%E5%99%A8_(C%2B%2B))
The primary reason for defining custom allocators is to improve performance. Utilizing a dedicated custom allocator can increase program performance, or improve memory usage efficiency, or both [4][8]. The default allocator uses the `new` operator to allocate storage [Reference 5], which is often implemented using the C language heap allocation function (malloc()) [9]. Because heap allocation functions are optimized for occasional large memory allocations, the default allocator generally works well when allocating memory for containers that require a single large memory allocation at once, such as vectors and doubly-ended queues [8]. However, for associative containers and linked lists that frequently allocate small amounts of memory, using the default allocator typically results in low efficiency [4][9]. In addition, the malloc()-based default allocator also has many problems, such as poor reference locality [4], and may cause memory fragmentation [4][9].
In short, this section (…)(like) is a “Dream” speech for this standard regarding allocators. Before dreams come true, programmers concerned with portability will be limited to using stateless custom allocators.
— Scott Meyers, *Effective STL*
Given this, in this situation, people often use memory pool-based allocators to solve the problem of frequent small allocations [8]. Unlike the default “on-demand” allocation method, when using a memory pool-based allocator, the program pre-allocates large blocks of memory (i.e., "memory pool") and then the custom allocator simply returns a pointer to an available memory location in the pool to the requester. When objects are destructed, it does not actually deallocate memory; instead, it is deferred until the lifetime of the memory pool ends [Note 1][8].
In the topic of “custom allocators,” many C++ experts and authors have participated in discussions, such as Scott Meyers’s *Effective STL* and Andrej Alexandrescu’s *Modern C++ Design*, which mention it. Meyers realized that if an allocator instance is required to be equal for a specific type T, the portable allocator instance must not contain state. Although the C++ standard encourages library implementers to support stateful allocators [Reference 4], Meyers said that this paragraph is “(seemingly) a wonderful view,” but it’s almost nonsense, and he considered the restrictions on allocators “too strict” [4]. For example, the list in STL allows the `splice` method, which means a node in one list object A can be directly moved into another list object B, which requires that the memory allocated by A's allocator be released by B's allocator, thus deducing that the allocator instances of A and B must be equal. Meyers’s conclusion is that allocators should be defined as types using static methods. For example, according to the C++ standard, the allocator must provide a `rebind` method other class template.
Furthermore, in *C++ Programming Language*, Bjørn Strubø suggests “‘restricting the allocator to avoid different information for each object’ is obviously not a problem” (roughly), and points out that most allocators do not need state, or even perform better without state. He proposed three use cases for custom allocators: memory pool-type allocators, shared memory-type allocators, and garbage collection-type allocators, and demonstrated an implementation of an allocator using an internal memory pool to quickly allocate/deallocate small amounts of memory [3]. However, he also mentioned that such optimization may already be implemented in the sample allocator he provided.
Another use for custom allocators is to debug memory-related errors [10]. To do this, you can write an allocator that allocates extra memory when allocating and stores debugging information. This type of allocator not only ensures that memory is allocated/deallocated by the same type of allocator, but also can protect the program to some extent from cache overflows [11].