PersistMemoryManager is a header-only C++20 library for persistent heap memory management.
All metadata is stored inside the managed region, which allows saving and loading a memory
image from a file or shared memory. Interaction with data in managed memory is done through
persistent typed pointers pptr<T>.
The manager is a fully static class template — there are no instances, no raw pointers in
user code. All API is accessible through static methods. Multiple independent manager
instances of the same configuration can coexist through the InstanceId template parameter
(multiton pattern).
Modular headers (include individually as needed):
#include "pmm/persist_memory_manager.h" // core manager (includes pmap, pstringview)
#include "pmm/manager_configs.h" // predefined configurations
#include "pmm/pmm_presets.h" // named preset aliases
#include "pmm/io.h" // file save / load utilities
#include "pmm/avl_tree_mixin.h" // shared AVL helpers (included via pmap/pstringview)Single-header presets (include one file, get a ready-to-use manager type).
Located in single_include/pmm/:
#include "pmm.h" // full library, any configuration
#include "pmm_single_threaded_heap.h" // SingleThreadedHeap preset
#include "pmm_multi_threaded_heap.h" // MultiThreadedHeap preset
#include "pmm_embedded_heap.h" // EmbeddedHeap preset
#include "pmm_industrial_db_heap.h" // IndustrialDBHeap preset
#include "pmm_embedded_static_heap.h" // EmbeddedStaticHeap preset (no heap, 32-bit)
#include "pmm_small_embedded_static_heap.h" // SmallEmbeddedStaticHeap (no heap, 16-bit)
#include "pmm_large_db_heap.h" // LargeDBHeap preset (64-bit index)Namespace: pmm
namespace pmm {
template <typename ConfigT = CacheManagerConfig, std::size_t InstanceId = 0>
class PersistMemoryManager;
}A fully static class template. All state (storage backend, mutex, initialization flag) is
stored in static inline members. No instances need to be created. Each unique combination
of ConfigT and InstanceId is an independent manager with its own separate storage.
Template parameters:
ConfigT— configuration struct that provides:address_traits— address space type (index size, granule size)storage_backend— storage backend type (HeapStorage, StaticStorage, MMapStorage)free_block_tree— free block search policy (AvlFreeTree)lock_policy— thread safety policy (NoLock, SharedMutexLock)granule_size— granule size in bytesgrow_numerator/grow_denominator— growth ratio
InstanceId— instance identifier (default0). Allows multiple independent managers with the same configuration.
Nested type aliases:
// Persistent typed pointer bound to this manager
template <typename T>
using pptr = pmm::pptr<T, PersistMemoryManager>;
// Persistent interned read-only string
using pstringview = pmm::pstringview<PersistMemoryManager>;
// Persistent AVL tree dictionary
template <typename _K, typename _V>
using pmap = pmm::pmap<_K, _V, PersistMemoryManager>;
static bool create(std::size_t initial_size) noexcept;Initializes the manager with the given initial size. Allocates the storage backend and sets up the memory layout.
Parameters:
initial_size— initial size in bytes. Must be>= kMinMemorySize(4096).
Returns: true on success, false on error.
Example:
using MyMgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
bool ok = MyMgr::create(1024 * 1024); // 1 MiBstatic bool create() noexcept;Initializes the manager over an already-allocated backend buffer. Use this when the storage backend has been set up externally (e.g., MMapStorage or StaticStorage).
Returns: true on success, false if the backend is not ready.
static bool load(VerifyResult& result) noexcept;Loads an existing manager state from the backend buffer. Validates the magic number,
total size, and granule size. Rebuilds the free block AVL tree, repairs the linked list,
and recomputes counters. Detected violations and repair actions are reported through
result.
Returns: true on success, false if the image is invalid.
Example:
// After filling the backend buffer with a saved image:
pmm::VerifyResult diagnostics;
bool ok = MyMgr::load(diagnostics);static void destroy() noexcept;Resets the runtime manager state. Clears the initialization flag. Does not free the
backend buffer and does not modify the persisted image, so a valid backend image
remains loadable with load(result). Required for test isolation, normal shutdown, and
before re-initialization.
Example:
MyMgr::destroy();static void destroy_image() noexcept;Explicitly invalidates the current backend image by clearing the header magic, then
resets the runtime manager state. This is a destructive helper for tests and corruption
simulation. Use destroy() for normal shutdown.
Example:
MyMgr::destroy_image(); // subsequent load(result) fails with InvalidMagicstatic bool is_initialized() noexcept;Returns true if the manager has been initialized via create() or load().
template <typename T>
static pptr<T> allocate_typed() noexcept;
template <typename T>
static pptr<T> allocate_typed(std::size_t count) noexcept;Allocates sizeof(T) bytes (or sizeof(T) * count for arrays) aligned to the granule
size. If memory is insufficient, the manager automatically expands the storage backend.
Returns: pptr<T> pointing to the allocated block, or a null pptr<T>() on error.
Example:
using MyMgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
MyMgr::create(1024 * 1024);
MyMgr::pptr<int> p1 = MyMgr::allocate_typed<int>();
MyMgr::pptr<double> p2 = MyMgr::allocate_typed<double>(10); // array of 10 doubles
*p1 = 42;
p2[1] = 3.14;template <typename T>
static void deallocate_typed(pptr<T> p) noexcept;Frees the block pointed to by p. A null pptr is a no-op. Permanently locked blocks
(see lock_block_permanent) cannot be freed.
Example:
MyMgr::deallocate_typed(p1);There is no reallocate_typed method in the current API. To resize, allocate a new block,
copy data manually, and deallocate the old block.
static void* allocate(std::size_t user_size) noexcept;Allocates user_size bytes and returns a raw pointer. Lower-level than allocate_typed.
Returns: pointer to user data, or nullptr on error.
static void deallocate(void* ptr) noexcept;Frees the block at ptr. Null pointer is a no-op.
static bool lock_block_permanent(void* ptr) noexcept;Permanently locks a block, making it impossible to free via deallocate(). Intended for
blocks containing permanent data (e.g., a persistent string dictionary). The block's
node_type is set to kNodeReadOnly.
Returns: true if the block was successfully locked, false if not found or already free.
static bool is_permanently_locked(const void* ptr) noexcept;Returns true if the block at ptr is permanently locked (node_type == kNodeReadOnly).
template <typename T>
static T* resolve(pptr<T> p) noexcept;Converts a persistent pointer to a raw pointer. Called internally by pptr<T>::resolve(),
operator*, and operator->.
Returns: T* pointer to user data, or nullptr for a null or uninitialized manager.
template <typename T>
static T* resolve_at(pptr<T> p, std::size_t i) noexcept;Returns a pointer to the i-th element of the array pointed to by p.
All statistics methods are static and thread-safe (use shared_lock).
static std::size_t total_size() noexcept;Total size of the managed region in bytes. Returns 0 if not initialized.
static std::size_t used_size() noexcept;Amount of used memory: block headers plus user data, in bytes.
static std::size_t free_size() noexcept;Amount of available free memory in bytes.
Invariant: used_size() + free_size() <= total_size().
static std::size_t block_count() noexcept;Total number of blocks (both allocated and free).
static std::size_t free_block_count() noexcept;Number of free blocks.
static std::size_t alloc_block_count() noexcept;Number of allocated blocks.
template <typename Callback>
static bool for_each_block(Callback&& callback) noexcept;
// Callback: void(const pmm::BlockView&)Iterates all blocks in address order (from smallest to largest offset) and calls
callback for each. Thread-safe (shared_lock).
Returns: false if not initialized, true otherwise.
Note: Do not call allocate or deallocate from the callback — this will cause a deadlock.
template <typename Callback>
static bool for_each_free_block(Callback&& callback) noexcept;
// Callback: void(const pmm::FreeBlockView&)Iterates free blocks in the AVL tree in-order (by ascending block size) and calls
callback for each.
Returns: false if not initialized, true otherwise.
These methods allow reading and writing AVL tree metadata for a block pointed to by a pptr. They are intended for advanced use cases, such as implementing persistent data structures (e.g., a persistent AVL tree using PMM blocks as nodes).
Warning: Modifying AVL tree fields on regular allocated blocks can corrupt the free block tree. Only use these methods on blocks that are permanently locked via
lock_block_permanent().
template <typename T>
static BlockHeader<address_traits>& tree_node(pptr<T> p) noexcept;Returns a direct reference to the BlockHeader of the
block backing p. The AVL slot is the prefix of BlockHeader<AT>, not a separate
TreeNode object. AVL fields are accessed as direct data members of the returned reference:
auto& h = MyMgr::tree_node(p);
h.left_offset; // index_type — left child granule index or no_block
h.right_offset; // index_type — right child granule index or no_block
h.parent_offset; // index_type — parent granule index or no_block
h.weight; // index_type — node weight (also data granules for allocated blocks)
h.avl_height; // std::int16_t — AVL subtree height
h.left_offset = idx;
h.right_offset = idx;
h.parent_offset = idx;
h.weight = w;
h.avl_height = height;Warning: The returned reference is only valid while the manager is initialized and the block has not been freed. Do not store the reference beyond the current operation.
Absent links are stored as
address_traits::no_blocksentinel, not as zero.
static storage_backend& backend() noexcept;Returns a reference to the static storage backend. For advanced scenarios (e.g., accessing
MMapStorage to get base_ptr() before calling load()).
namespace pmm {
template <class T, class ManagerT>
requires (!std::is_void_v<ManagerT>)
class pptr;
}A persistent typed pointer. Stores a granule index (offset-based, not address-based), which makes it address-independent: it remains valid after loading the image at a different base address.
Requirement: sizeof(pptr<T, ManagerT>) == sizeof(index_type) (2, 4, or 8 bytes
depending on address_traits).
The preferred way to obtain a pptr is through the nested alias in the manager:
using MyMgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
MyMgr::pptr<int> p = MyMgr::allocate_typed<int>();using element_type = T;
using manager_type = ManagerT;
using index_type = typename ManagerT::address_traits::index_type; // uint16_t, uint32_t, or uint64_tconstexpr pptr() noexcept; // null pointer (index 0)
explicit constexpr pptr(index_type idx) noexcept; // from granule index
pptr(const pptr&) = default;
pptr& operator=(const pptr&) = default;bool is_null() const noexcept;
explicit operator bool() const noexcept; // true if not nullindex_type offset() const noexcept; // granule index of user dataT& operator*() const noexcept; // dereference
T* operator->() const noexcept; // member accessBoth operations call ManagerT::resolve<T>(p) internally.
BlockHeader<address_traits>& tree_node() const noexcept; // direct reference to BlockHeaderReturns a reference to the BlockHeader of the block
backing p. The AVL slot is the prefix of BlockHeader<AT> (fields weight,
left_offset, right_offset, parent_offset, avl_height); there is no separate
TreeNode object. AVL fields are accessed as direct data members:
auto& h = p.tree_node();
h.left_offset; // index_type — left child granule index or no_block
h.right_offset; // index_type — right child granule index or no_block
h.parent_offset; // index_type — parent granule index or no_block
h.weight; // index_type — node weight
h.avl_height; // std::int16_t — AVL subtree height
h.left_offset = idx;
h.right_offset = idx;
h.parent_offset = idx;
h.weight = w;
h.avl_height = height;Note: Absent links are stored as
address_traits::no_blocksentinel. Usep.offset()to convert a pptr to a granule index for storage in tree fields.
bool operator==(const pptr<T, ManagerT>& other) const noexcept;
bool operator!=(const pptr<T, ManagerT>& other) const noexcept;using MyMgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
MyMgr::create(1 << 20);
// Allocate and use
MyMgr::pptr<int> p = MyMgr::allocate_typed<int>();
*p = 123;
// Save granule index for later recovery
uint32_t saved_idx = p.offset();
// Save image to file
pmm::save_manager<MyMgr>("heap.dat");
MyMgr::destroy();
// Restore from file
MyMgr::create(1 << 20);
pmm::load_manager_from_file<MyMgr>("heap.dat");
MyMgr::pptr<int> p2(saved_idx);
assert(*p2 == 123);
MyMgr::destroy();namespace pmm {
template <typename ManagerT>
struct pstringview;
}A persistent interned read-only string. Provides automatic deduplication: two calls to
pstringview("hello") return the same pptr (same granule index). The string data and
the pstringview blocks are permanently locked via lock_block_permanent().
Accessed via manager nested alias:
using Mgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
Mgr::pptr<Mgr::pstringview> p = Mgr::pstringview("hello");std::uint32_t length; // string length (without null terminator)
char str[1]; // embedded null-terminated chars (flexible-array pattern)explicit pstringview(const char* s) noexcept;Creates a temporary stack object that interns s into PAP. Converts implicitly to
pptr<pstringview<ManagerT>>:
Mgr::pptr<Mgr::pstringview> p = Mgr::pstringview("world");const char* c_str() const noexcept; // raw C-string pointer (valid while manager is initialized)
std::size_t size() const noexcept; // length without null terminator
bool empty() const noexcept; // true if length == 0bool operator==(const char* s) const noexcept;
bool operator==(const pstringview& o) const noexcept;
bool operator!=(const char* s) const noexcept;
bool operator!=(const pstringview& o) const noexcept;
bool operator<(const pstringview& o) const noexcept; // lexicographic; for use as pmap keystatic psview_pptr intern(const char* s) noexcept; // explicit interning
static void reset() noexcept; // clears system/symbols domain root (tests)using Mgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
Mgr::create(64 * 1024);
Mgr::pptr<Mgr::pstringview> p = Mgr::pstringview("hello");
Mgr::pptr<Mgr::pstringview> p2 = Mgr::pstringview("hello");
assert(p == p2); // same pptr — deduplication guaranteed
assert(p->size() == 5);
assert(p->c_str()[0] == 'h');
Mgr::destroy();namespace pmm {
template <typename _K, typename _V, typename ManagerT>
struct pmap;
}A persistent associative container (dictionary) based on an AVL tree. Each node is an
allocated block in PAP containing a key-value pair. The built-in AVL slot of the
BlockHeader of each block (fields weight,
left_offset, right_offset, parent_offset, avl_height) serves as the AVL tree links
(no separate node allocations). Inserting a duplicate key updates the existing value.
pmap is a typed facade over the canonical forest-domain protocol. Its AVL root is stored
in a manager domain binding under container/pmap/<type>/<binding>, not in the pmap
object. The object stores only the domain identity, so separate map objects and different
_K/_V node layouts do not share a root unless they are copied from the same facade or
constructed with the same named domain key.
The <type> segment is a deterministic fingerprint derived from sizeof, alignof, and
standard <type_traits> categories — not from compiler-specific spellings such as
__PRETTY_FUNCTION__ or __FUNCSIG__. For PODs that would otherwise collide on this
fingerprint, or any application that wants hard-pinned persistent type identity, specialize
pmm::pmap_type_identity<T>::tag with a fixed ASCII string:
namespace pmm {
template <> struct pmap_type_identity<MyPayload> {
static constexpr const char* tag = "myapp/MyPayload/v1";
};
}Accessed via manager nested alias:
using Mgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
Mgr::pmap<int, int> map;
map.insert(42, 100);Key type requirements: _K must support operator< and operator==.
const char* domain_name() const noexcept; // actual registry domain name; empty until bound
index_type root_index() const noexcept; // current domain root; 0 = empty mappmap() noexcept; // generates an independent domain on first mutable access
explicit pmap(const char* domain_key) noexcept; // stable named domain when ManagerT is initializedbool empty() const noexcept;
node_pptr insert(const _K& key, const _V& val) noexcept; // insert or update; returns node pptr
node_pptr find (const _K& key) const noexcept; // returns null pptr if not found
bool contains(const _K& key) const noexcept;
bool erase(const _K& key) noexcept; // O(log n) remove by key; returns false if not found
std::size_t size() const noexcept; // O(n) element count via avl_subtree_count
void clear() noexcept; // O(n) remove all elements with deallocation
void reset() noexcept; // reset root for test isolationUses the shared AvlInorderIterator<NodePPtr> template from avl_tree_mixin.h:
using iterator = pmm::detail::AvlInorderIterator<node_pptr>;
iterator begin() const noexcept; // leftmost node (smallest key)
iterator end() const noexcept; // sentinel (null)The iterator traverses nodes in ascending key order via avl_inorder_successor.
template <typename _K, typename _V>
struct pmap_node {
_K key;
_V value;
};Each node is a separate PAP block. Access via the returned pptr:
auto p = map.find(42);
if (!p.is_null()) {
int val = p->value; // 100
}using Mgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
Mgr::create(64 * 1024);
Mgr::pmap<int, int> map;
map.insert(42, 100);
map.insert(10, 200);
map.insert(42, 300); // updates existing value
auto p = map.find(42);
assert(!p.is_null() && p->value == 300);
assert(map.contains(10));
assert(!map.contains(99));
// Size and iteration
assert(map.size() == 2);
for (auto it = map.begin(); it != map.end(); ++it) {
auto node = *it;
// node->key, node->value — in ascending key order
}
// Erase and clear
map.erase(42); // true — removes node and deallocates
map.clear(); // removes all remaining elements
assert(map.empty());
Mgr::destroy();Using pstringview as a key:
using Mgr = pmm::PersistMemoryManager<pmm::CacheManagerConfig>;
Mgr::create(64 * 1024);
Mgr::pmap<Mgr::pstringview, int> dict;
Mgr::pptr<Mgr::pstringview> key = Mgr::pstringview("hello");
dict.insert(*key, 42);
auto p = dict.find(*key);
assert(!p.is_null() && p->value == 42);
Mgr::destroy();namespace pmm {
template <typename MgrT>
bool save_manager(const char* filename);
}Saves the entire managed memory image to a binary file. save_manager() takes
the manager lock, copies a stable snapshot, computes CRC32 on that copy, and
does not mutate the live manager header while saving. Since all metadata uses
offsets from the buffer start, the image can be loaded at any base address.
The save path writes filename.tmp, flushes it to stable storage, atomically
renames it to filename, and fsyncs the parent directory where the platform
supports that operation.
Parameters:
filename— path to output file. Must not benullptr.
Precondition: MgrT::is_initialized() == true.
Returns: true on success, false on I/O error or if not initialized.
Example:
#include "pmm/io.h"
if (!pmm::save_manager<MyMgr>("heap.dat")) {
// write error
}namespace pmm {
template <typename MgrT>
bool load_manager_from_file(const char* filename);
}Loads a manager image from a file into the backend buffer, then calls MgrT::load() to
validate the header and restore state.
Precondition: The backend must have an allocated buffer of sufficient size. For
HeapStorage, call MgrT::create(size) before calling this function.
Parameters:
filename— path to the image file.
Returns: true on success, false on error.
Example:
#include "pmm/io.h"
MyMgr::create(1024 * 1024); // allocate buffer
bool ok = pmm::load_manager_from_file<MyMgr>("heap.dat");
if (ok) {
// manager restored from file
}Ready-to-use type aliases in namespace pmm::presets:
| Type | Lock policy | Growth | Index | Use case |
|---|---|---|---|---|
SingleThreadedHeap |
NoLock | 25% | 32-bit | Single-threaded caches, tools |
MultiThreadedHeap |
SharedMutexLock | 25% | 32-bit | Concurrent services |
EmbeddedHeap |
NoLock | 50% | 32-bit | Memory-constrained devices |
IndustrialDBHeap |
SharedMutexLock | 100% | 32-bit | High-throughput databases |
EmbeddedStaticHeap<N> |
NoLock | — | 32-bit | Embedded without heap (static buffer) |
SmallEmbeddedStaticHeap<N> |
NoLock | — | 16-bit | Tiny embedded, up to ~1 MB |
LargeDBHeap |
SharedMutexLock | 100% | 64-bit | Petabyte-scale databases |
Example using a preset:
#include "pmm/pmm_presets.h"
using Heap = pmm::presets::SingleThreadedHeap;
Heap::create(64 * 1024); // 64 KiB
Heap::pptr<int> p = Heap::allocate_typed<int>();
*p = 99;
Heap::deallocate_typed(p);
Heap::destroy();Or using single-header preset files:
#include "pmm_single_threaded_heap.h"
pmm::presets::SingleThreadedHeap::create(64 * 1024);Describes a block when iterating via for_each_block():
struct BlockView {
std::uint32_t index; // granule index of the block header
std::ptrdiff_t offset; // byte offset from buffer start
std::size_t total_size; // total block size in bytes (header + data)
std::size_t header_size; // block header size in bytes (sizeof(Block<A>))
std::size_t user_size; // user data size in bytes (0 if free)
std::size_t alignment; // granule size (alignment)
bool used; // true if allocated, false if free
};Describes a free block when iterating via for_each_free_block():
struct FreeBlockView {
std::ptrdiff_t offset; // byte offset from buffer start
std::size_t total_size; // total block size in bytes
std::size_t free_size; // available user data size in bytes
std::ptrdiff_t left_offset; // left child byte offset (-1 if none)
std::ptrdiff_t right_offset; // right child byte offset (-1 if none)
std::ptrdiff_t parent_offset; // parent byte offset (-1 if none)
std::int16_t avl_height; // AVL subtree height
int avl_depth; // depth from root (0 = root)
};| Config struct | Lock | Growth | Storage | Index | Use case |
|---|---|---|---|---|---|
CacheManagerConfig |
NoLock | 25% | HeapStorage | 32-bit | Single-threaded cache |
PersistentDataConfig |
SharedMutexLock | 25% | HeapStorage | 32-bit | Multi-threaded persistent storage |
EmbeddedManagerConfig |
NoLock | 50% | HeapStorage | 32-bit | Embedded systems |
IndustrialDBConfig |
SharedMutexLock | 100% | HeapStorage | 32-bit | Industrial databases |
EmbeddedStaticConfig<N> |
NoLock | — | StaticStorage | 32-bit | Embedded without heap, fixed pool |
SmallEmbeddedStaticConfig<N> |
NoLock | — | StaticStorage | 16-bit | Tiny embedded, up to ~1 MB |
LargeDBConfig |
SharedMutexLock | 100% | HeapStorage | 64-bit | Petabyte-scale databases |
| Traits alias | Index type | Granule | Max addressable | Use case |
|---|---|---|---|---|
TinyAddressTraits |
uint8_t |
8 B | ~2 KB | Experimental / ultra-tiny embedded |
SmallAddressTraits |
uint16_t |
16 B | ~1 MB | Tiny embedded, microcontrollers |
DefaultAddressTraits |
uint32_t |
16 B | 64 GB | General purpose (default) |
LargeAddressTraits |
uint64_t |
64 B | Petabyte+ | Large-scale databases |
sizeof(pptr<T, Mgr>) equals sizeof(index_type): 1, 2, 4, or 8 bytes respectively.
| Constant | Value | Description |
|---|---|---|
kGranuleSize |
16 | Default granule size in bytes (DefaultAddressTraits) |
kMinAlignment |
16 | Minimum alignment in bytes |
kMinMemorySize |
4096 | Minimum buffer size in bytes |
kMinBlockSize |
16 | Minimum user data block size in bytes |
kMinGranuleSize |
4 | Minimum allowed granule size (architecture word size) |
config::kDefaultGrowNumerator |
5 | Growth ratio numerator (5/4 = 25%) |
config::kDefaultGrowDenominator |
4 | Growth ratio denominator |
| Condition | Behavior |
|---|---|
create(size < 4096) |
Returns false |
create() with no backend buffer |
Returns false |
load() with invalid magic |
Returns false |
load() with unsupported image version |
Returns false |
load() with mismatched total size |
Returns false |
load() with mismatched granule size |
Returns false |
allocate_typed<T>() when out of memory |
Auto-expands by growth ratio |
allocate_typed<T>(0) |
Returns null pptr |
deallocate_typed(null pptr) |
No-op |
deallocate on permanently locked block |
No-op |
save_manager(nullptr) |
Returns false |
load_manager_from_file(nullptr) |
Returns false |
load_manager_from_file(nonexistent file) |
Returns false |
load_manager_from_file(file > buffer size) |
Returns false |
pstringview(nullptr) |
Treated as "" |
pmap::find(key) for missing key |
Returns null pptr |
pmap::insert(key, val) for existing key |
Updates value |
EmbeddedStaticConfig backend expansion |
Always fails (StaticStorage::expand() returns false) |
Thread safety depends on the lock_policy in the configuration:
-
SharedMutexLock: All public methods are thread-safe using
std::shared_mutex. Read operations (total_size,used_size,free_size,block_count,free_block_count,alloc_block_count,for_each_block,for_each_free_block,is_initialized,resolve,tree_node,is_permanently_locked) acquire ashared_lockand can run concurrently. Write operations (create,load,destroy,allocate,deallocate,allocate_typed,deallocate_typed,lock_block_permanent) acquire aunique_lock. Note:tree_node()returns a reference — writes through that reference are not guarded by the manager lock. -
NoLock: No synchronization is performed. All operations are safe only in single-threaded contexts.
Do not call allocate or deallocate from inside
for_each_blockorfor_each_free_blockcallbacks — this will deadlock under SharedMutexLock.
pstringview and pmap are not independently thread-safe — their safety depends entirely on the manager's lock policy.
- I/O uses stdio only (
fopen/fread/fwrite/fclose). - Free block search: best-fit via AVL tree — O(log n).
- No image compression or encryption.
- Only one active instance per
(ConfigT, InstanceId)specialization at a time. - Maximum addressable memory with
DefaultAddressTraits: 64 GB (2³² × 16 bytes/granule). - Maximum addressable memory with
LargeAddressTraits: petabyte scale. - Maximum addressable memory with
SmallAddressTraits: ~1 MB. - Requires C++20 compiler (GCC 10+, Clang 10+, MSVC 2019 16.3+).