🟩 Heap Allocation, Custom allocators, Virtual Memory, Buffer, Segment
Memory library helps tracking and limiting runtime / dynamic allocations through the use of custom allocators.
If a library doesn't directly or indirectly depend on the Memory library, you can assume that it will not do any runtime / dynamic allocation.
Most classes have been originally part of the Foundation library.
Features
Classes
Class | Description |
SC::Buffer | An heap allocated byte buffer that can optionally use an inline buffer. |
SC::Memory | Centralized functions to allocate, reallocate and deallocate memory. |
SC::VirtualMemory | Reserves a contiguous slice of virtual memory committing just a portion of it. |
SC::Globals | Customizable thread-local and global variables for memory handling. |
Status
🟩 Usable
The library is solid. The Buffer implementation has been evolved and fine tuned to be minimal but effective.
Description
Memory library helps tracking and limit runtime / dynamic allocations through the use of custom allocators. A classic dynamically expandable binary buffer SC::Buffer is provided and it's largely shared to form the more object model oriented SC::Vector class from Containers Library.
All allocations throughout all downstream dependant libraries are centrally tracked by the SC::Globals class, that also allows re-defining custom thread-local allocators.
Such allocators can be just fixed buffers, regular heap memory or reserved SC::VirtualMemory using only limited amounts of Physical memory.
Buffer
An heap allocated byte buffer that can optionally use an inline buffer.
- See also
- SC::SmallBuffer to use an inline buffer that can optionally become heap allocated as needed.
- Note
- This class (and SC::SmallBuffer) reduces needs for the header-only SC::Vector (from Containers). SC::Buffer avoids some compile time / executable size bloat because it's not header only.
Example:
bool funcRequiringBuffer(Buffer& buffer)
{
for (size_t idx = 0; idx < buffer.size(); ++idx)
{
if (buffer[idx] != 123)
return false;
}
return true;
}
void BufferTest::basic()
{
Buffer buffer;
buffer.clear();
funcRequiringBuffer(buffer);
SmallBuffer<128> smallBuffer;
smallBuffer = buffer;
funcRequiringBuffer(smallBuffer);
Buffer buffer2;
smallBuffer =
move(buffer2);
}
Memory
Centralized functions to allocate, reallocate and deallocate memory.
VirtualMemory
Reserves a contiguous slice of virtual memory committing just a portion of it.
This class is useful on 64-bit systems where the address space is so large that it's feasible reserving large chunks of memory to commit and de-commit (shrink) as needed.
Reservation ensures that the returned address will not change and will be sized in multiples of system page size.
- Note
- Memory must be committed in order to be read or written, occupying physical memory pages.
- Warning
- This class has no defined destructor so memory MUST be released calling VirtualMemory::release
const size_t moreThanOnePageSize = VirtualMemory::getPageSize() + 1024;
const size_t lessThanOnePageSize = VirtualMemory::getPageSize() - 1024;
void* reference = Memory::allocate(moreThanOnePageSize, 1);
memset(reference, 1, moreThanOnePageSize);
auto releaseLater =
MakeDeferred([&] { Memory::release(reference); });
SC_TEST_EXPECT(virtualMemory.reserve(2 * VirtualMemory::getPageSize()));
char* memory = static_cast<char*>(virtualMemory.memory);
memset(memory, 1, lessThanOnePageSize);
memset(memory + lessThanOnePageSize, 1, moreThanOnePageSize - lessThanOnePageSize);
Globals
Customizable thread-local and global variables for memory handling.
This class holds pointers to systems that must be globally available, like the memory allocator. It allows "stacking" different Globals through a push / pop mechanism, connecting them through a linked list. The Default allocator is automatically setup and uses standard malloc
, realloc
, free
for allocations.
- Note
- Globals use no locking mechanism so they are thread-unsafe. Every method however requires a Globals::Type parameter that can be set to Globals::ThreadLocal to avoid such issues.
Example (Fixed Allocator):
alignas(
uint64_t)
char stackMemory[256] = {0};
FixedAllocator fixedAllocator = {stackMemory, sizeof(stackMemory)};
Globals globals = {fixedAllocator};
Globals::push(Globals::Global, globals);
Buffer& buffer = *Globals::get(Globals::Global).allocator.create<Buffer>();
(void)buffer.append({"ASDF"});
Globals::pop(Globals::Global);
Example (Virtual Allocator):
VirtualMemory virtualMemory;
VirtualAllocator virtualAllocator = {virtualMemory};
Globals virtualGlobals = {virtualAllocator};
Globals::push(Globals::Global, virtualGlobals);
Buffer& buffer = *Globals::get(Globals::Global).allocator.create<Buffer>();
(void)buffer.append({"ASDF"});
Globals::pop(Globals::Global);
Example (Memory dump):
struct NestedStruct
{
};
struct ComplexStruct
{
int someField = 0;
NestedStruct nestedStruct;
};
Globals::push(Globals::Global, globals);
ComplexStruct&
object = *allocator.
create<ComplexStruct>();
object.someField = 42;
object.singleString = "ASDF";
object.someStrings = {"First", "Second"};
SC_TEST_EXPECT(
object.nestedStruct.someMap.insertIfNotExists({
"1", 1}));
SC_TEST_EXPECT((
size_t(memoryDump.data()) %
alignof(ComplexStruct)) == 0);
Globals::pop(Globals::Global);
const ComplexStruct& readonly = *span.start_lifetime_as<const ComplexStruct>();
ComplexStruct modifiable = readonly;
modifiable.someStrings[0] = "First modified";
Blog
Some relevant blog posts are:
(These blogs were written before the split from Foundation into the Memory library)
Roadmap
🟦 Complete Features:
- Things will be added as needed
💡 Unplanned Features:
- Note
- In Principles there is a rule that discourages allocations of large number of tiny objects and also creating systems with unclear or shared memory ownership. For this reason this library is missing Smart Pointers.