Skip to content

decodeless/allocator

Repository files navigation

decodeless_allocator

decodeless is a collection of utility libraries for conveniently reading and writing files via memory mapping. Components can be used individually or combined.

decodeless_allocator is a possibly-growable local linear arena allocator.

  • growable: The backing allocation may grow if it has reallocate() and the call returns the same address.
  • local: Has per-allocator-instance state
  • linear: Gives monotonic/sequential but aligned allocations that cannot be freed or reused. There is only a reset() call. Only trivially destructible objects should be created from this.
  • arena: Allocations come from a single blob/pool and when it is exhausted std::bad_alloc is thrown (unless a reallocate() is possible).
  • STL compatible, although this probably isn't useful as it's a linear allocator.
  • Remote allocator compatible, to allocate for another virtual address space.

But why? I want to write C++ structures to a file and read them back without decoding or deserializing byte by byte because that's slow. If I read it back into memory I must take care of alignment. Allocators do that already and a linear allocator would tightly pack the data for a file. In fact, allocators can be chained, so the data it allocates could come directly from a memory mapped file. That is exactly what decodeless_writer does, using this library internally.

Note: objects created by the allocator must be trivially destructible because no destructor is ever called. This is by design because it is expected objects are written to disk.

For allocator type erasure, decodeless/pmr_allocator.hpp implements std::pmr::memory_resource. This allows non-templated code to allocate from different allocator types (e.g. via std::pmr::polymorphic_allocator) and without including the allocator's header. Note that decodeless_allocator requires trivially destructible types but std::pmr::polymorphic_allocator hides this validation otherwise enforced by decodeless::linear_allocator.

This library includes utility functions decodeless::create::object() and decodeless::create::array() to construct objects from an allocator or memory resource.

The linear allocator can be backed by a remote parent allocator, e.g. allocating memory for another virtual address space such as video memory. It does not read/write to the memory it allocates from. Note that decodelsss::linear_memory_resource can handle nullptr while it is UB for decodelsss::pmr_linear_memory_resource, which implements std::pmr::memory_resource, to receive nullptr as an allocation address. "Why would anyone want that?" I hear you screaming. It can be handy for two pass allocation where you are initially just interested in a total size and actually start with a zero base.

std::pmr::monotonic_buffer_resource is quite similar to decodelsss::pmr_linear_memory_resource. The primary difference is decodelsss::pmr_linear_memory_resource support growing via its reallocating interface. Secondly, it's a template and the backing resource can be inlined into the object, so there is less indirection and more compiler optimization. This applies to decodelsss::linear_memory_resource too.

Example

// could also be decodeless::mapped_file_allocator<std::byte> from
// decodeless_writer
using parent_allocator = std::allocator<std::byte>;
decodeless::linear_memory_resource<parent_allocator> memory(1024);

std::span<int> array = decodeless::create::array<int>(memory, {1, 3, 6, 10, 15});
EXPECT_EQ(array.size(), 5);
EXPECT_EQ(array[4], 15);
EXPECT_EQ(memory.size(), sizeof(int) * 5);

double* alignedDouble = decodeless::create::object(memory, 42.0);
EXPECT_EQ(*alignedDouble, 42.0);
EXPECT_EQ(memory.size(), sizeof(int) * 5 + sizeof(double) + 4);

Using the polymorphic allocator:

decodeless::pmr_linear_memory_resource     res(100);
std::pmr::polymorphic_allocator<std::byte> alloc(&res); // interface abstraction
std::span<uint8_t> bytes = decodeless::create::array<uint8_t>(alloc, 10);
EXPECT_EQ(bytes.size(), 10);
EXPECT_EQ(res.size(), 10);

Dependencies

None. Just needs C++20.

Cmake Integration

This is a header only library with no dependencies other than C++20. A convenient and tested way to use the library is with cmake's FetchContent:

include(FetchContent)
FetchContent_Declare(
    decodeless_allocator
    GIT_REPOSITORY https://github.com/decodeless/allocator.git
    GIT_TAG release_tag
    GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(decodeless_allocator)

target_link_libraries(myproject PRIVATE decodeless::allocator)

If using in a library, a config file is provided for find_package(decodeless_allocator REQUIRED CONFIG PATHS ...), which trivially includes CMakeLists.txt. See decodeless_writer for an example.

memory_resource and allocator

A memory resource is the object that actually owns the memory being allocated. The allocator is a copyable pointer to the memory resource. This separation comes directly from consistency with STL allocators. Sometimes the allocator indirection is needed and sometimes inlining allocation code in the memory resource improves performance, e.g. when chaining allocators. For typical decodeless use cases, std::pmr::polymorphic_allocator is likely preferable. For convenience, decodeless::memory_resource and decodeless::allocator are actually C++ concepts.

Contributing

Issues and pull requests are most welcome, thank you! Note the DCO and MIT LICENSE.

About

An STL compatible growable local linear arena allocator for writing memory mapped files

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •