Date: Mon, 25 Aug 2025 18:17:39 -0700
Hello,
I'd like to describe a scheme for managing allocators I have seen used
in open world video games. It solves the same problems the
pmr::polymorphic_allocator does in an easier way. This is not a
request for a feature because I want it, I just think this scheme is
more practical than the standard and would like to share it as such.
This is intended for managing 1000s of allocations at once in a hard
real-time environment where a general purpose allocator is too slow
and is at risk of fragmenting.
The idea is to have a thread local identifier for the "current
allocator." Then an RAII class uses it to move all the code called
within a certain scope into a selected allocator. This implements a
stack of current allocators on the callstack. The previous allocator
can be stored in the RAII object and so an explicit stack is not
needed. In the same manner as the pmr code the current allocator's
type is removed from the container's type signature entirely. The
difference is that large quantities of code can be controlled without
changing a line of it (or adding virtual operations everywhere.)
In this example there are 10-20 stack allocators and each one is
assigned to a different region of the 3D world or to other kinds of
resources. Each resource is known to fit in advance and all references
to those resources must be somehow weak. Calls to delete are simply
counted or ignored if they fall within the address range used by the
stack allocators.
void update_loaded_regions(location_t location) {
// ... First determine if a new region must be loaded ...
// Find the least valuable region and reset the allocator for it.
Breaks references.
int free_region = evict_region(location);
// Replace the current allocator with free_region and store the
old value in the RAII object.
std::allocator_raii scope(free_region);
// Load a wide variety of types using a plugin system. Assume this
is a worker thread.
load_region(location, free_region);
// Safely return to using a temporary allocator when the scope closes.
}
My preference would be for the "current allocator" to be a program
specific integer because it is nobody's business talking directly to
it. The amount of code added to the pmr namespace would be a simple
RAII object for managing a thread local integer. It would be up to the
application to consult that integer in its implementation of its
container's Allocators or in its implementation of new/delete.
Obviously this doesn't have to be added to the standard. However, it
would provide a basis for sharing these semantics across third party
libraries. E.g. a third party library could be passed a couple ints to
use in a couple locations which may or may not actually do anything.
I am happy to write a proposal. I did put together an example
implementation of all this. It is more complicated if you are looking
for additional features.
https://github.com/whatchamacallem/hatchlingplatform/blob/master/include/hx/hxmemory_manager.h
Regards,
Adrian
I'd like to describe a scheme for managing allocators I have seen used
in open world video games. It solves the same problems the
pmr::polymorphic_allocator does in an easier way. This is not a
request for a feature because I want it, I just think this scheme is
more practical than the standard and would like to share it as such.
This is intended for managing 1000s of allocations at once in a hard
real-time environment where a general purpose allocator is too slow
and is at risk of fragmenting.
The idea is to have a thread local identifier for the "current
allocator." Then an RAII class uses it to move all the code called
within a certain scope into a selected allocator. This implements a
stack of current allocators on the callstack. The previous allocator
can be stored in the RAII object and so an explicit stack is not
needed. In the same manner as the pmr code the current allocator's
type is removed from the container's type signature entirely. The
difference is that large quantities of code can be controlled without
changing a line of it (or adding virtual operations everywhere.)
In this example there are 10-20 stack allocators and each one is
assigned to a different region of the 3D world or to other kinds of
resources. Each resource is known to fit in advance and all references
to those resources must be somehow weak. Calls to delete are simply
counted or ignored if they fall within the address range used by the
stack allocators.
void update_loaded_regions(location_t location) {
// ... First determine if a new region must be loaded ...
// Find the least valuable region and reset the allocator for it.
Breaks references.
int free_region = evict_region(location);
// Replace the current allocator with free_region and store the
old value in the RAII object.
std::allocator_raii scope(free_region);
// Load a wide variety of types using a plugin system. Assume this
is a worker thread.
load_region(location, free_region);
// Safely return to using a temporary allocator when the scope closes.
}
My preference would be for the "current allocator" to be a program
specific integer because it is nobody's business talking directly to
it. The amount of code added to the pmr namespace would be a simple
RAII object for managing a thread local integer. It would be up to the
application to consult that integer in its implementation of its
container's Allocators or in its implementation of new/delete.
Obviously this doesn't have to be added to the standard. However, it
would provide a basis for sharing these semantics across third party
libraries. E.g. a third party library could be passed a couple ints to
use in a couple locations which may or may not actually do anything.
I am happy to write a proposal. I did put together an example
implementation of all this. It is more complicated if you are looking
for additional features.
https://github.com/whatchamacallem/hatchlingplatform/blob/master/include/hx/hxmemory_manager.h
Regards,
Adrian
Received on 2025-08-26 01:17:52