MemArena provides a memory arena or pool for allocating memory. Internally
memory in large blocks and allocates pieces of those blocks when memory is requested. Upon
destruction all of the reserved memory is released which also destroys all of the allocated memory.
This is useful when the goal is any (or all) of trying to
- amortize allocation costs for many small allocations.
- create better memory locality for containers.
- de-allocate memory in bulk.
MemArena instance is constructed no memory is reserved. A hint can be provided so that the
first internal reservation of memory will have close to but at least that amount of free space
available to be allocated.
In normal use memory is allocated from
MemArena::alloc() to get chunks
of memory, or
MemArena::make() to get constructed class instances.
takes an arbitrary set of arguments which it attempts to pass to a constructor for the type
T after allocating memory (
sizeof(T) bytes) for the object. If there isn’t enough
free reserved memory, a new internal block is reserved. The size of the new reserved memory will be at least
the size of the currently reserved memory, making each reservation larger than the last.
The arena can be frozen using
MemArena::freeze() which locks down the currently reserved
memory and forces the internal reservation of memory for the next allocation. By default this
internal reservation will be the size of the frozen allocated memory. If this isn’t the best value a
hint can be provided to the
MemArena::freeze() method to specify a different value, in the
same manner as the hint to the constructor. When the arena is thawed (unfrozen) using
MemArena::thaw() the frozen memory is released, which also destroys the frozen allocated
memory. Doing this can be useful after a series of allocations, which can result in the allocated
memory being in different internal blocks, along with possibly no longer in use memory. The result
is to coalesce (or garbage collect) all of the in use memory in the arena into a single bulk
internal reserved block. This improves memory efficiency and memory locality. This coalescence is
- Freezing the arena.
- Copying all objects back in to the arena.
- Thawing the arena.
Because the default reservation hint is large enough for all of the previously allocated memory, all
of the copied objects will be put in the same new internal block. If this for some reason this
sizing isn’t correct a hint can be passed to
MemArena::freeze() to specify a different value
(if, for instance, there is a lot of unused memory of known size). Generally this is most useful for
data that is initialized on process start and not changed after process startup. After the process
start initilization, the data can be coalesced for better performance after all modifications have
been done. Alternatively, a container that allocates and de-allocates same sized objects (such as a
std::map) can use a free list to re-use objects before going to the
MemArena for more
memory and thereby avoiding collecting unused memory in the arena.
Other than a freeze / thaw cycle, there is no mechanism to release memory except for the destruction
MemArena. In such use cases either wasted memory must be small enough or temporary enough
to not be an issue, or there must be a provision for some sort of garbage collection.
MemArena is not as useful for classes that allocate their own internal memory
std::vector), which includes most container classes. One
container class that can be easily used is
IntrusiveDList because the links are in the
instance and therefore also in the arena.
Objects created in the arena must not have
delete called on them as this will corrupt
memory, usually leading to an immediate crash. The memory for the instance will be released when the
arena is destroyed. The destructor can be called if needed but in general if a destructor is needed
it is probably not a class that should be constructed in the arena. Looking at
IntrusiveDList again for an example, if this is used to link objects in the arena, there is
no need for a destructor to clean up the links - all of the objects will be de-allocated when the
arena is destroyed. Whether this kind of situation can be arranged with reasonable effort is a good
heuristic on whether
MemArena is an appropriate choice.
MemArena will normally allocate memory in successive chunks from an internal block, if the
allocation request is large (more than a memory page) and there is not enough space in the current
internal block, a block just for that allocation will be created. This is useful if the purpose of
MemArena is to track blocks of memory more than reduce the number of system level allocations.
Construct a memory arena. n is optional. Initially not memory is reserved. If n is provided this is a hint that the first internal memory reservation should provide roughly and at least n bytes of free space. Otherwise the internal default hint is used. A call to
alloc(0)will not allocate memory but will force the reservation of internal memory if this should be done immediately rather than lazily.
Allocate memory of size n bytes in the arena. If n is zero then internal memory will be reserved if there is currently none, otherwise it is a no-op.
T, typename ...
Create an instance of T.
sizeof(T)bytes of memory are allocated from the arena and the constructor invoked. This method takes any set of arguments, which are passed to the constructor. A pointer to the newly constructed instance of T is returned. Note if the instance allocates other memory that memory will not be in the arena. Example constructing a
std::string_view * sv = arena.make<std::string_view>(pointer, n);
Stop allocating from existing internal memory blocks. These blocks are now “frozen”. Further allocation calls will cause new memory to be reserved.
n is optional. If not provided, make the hint for the next internal memory reservation to be large enough to hold all currently (now frozen) memory allocation. If n is provided it is used as the reservation hint.
Release all memory, destroying all allocations. The next memory reservation will be the size of the allocated memory (frozen and not) at the time of the call to
MemArena::clear(). n is optional. If this is provided it is used as the hint for the next reserved block, otherwise the hint is the size of all allocated memory.
Allocated memory is tracked by two linked lists, one for current memory and the other for frozen
memory. The latter is used only while the arena is frozen. Because a shared pointer is used for the
link, the list can be de-allocated by clearing the head pointer in
MemArena. This pattern is
similar to that used by the
IOBuffer data blocks, and so those were considered for use as
the internal memory allcation blocks. However, that would have required some non-trivial tweaks and,
with the move away from internal allocation pools to memory support from libraries like “jemalloc”,
unlikely to provide any benefit.