docs for the allocator, minor cleanup

v1
Brett 2024-03-08 22:34:50 -05:00
parent 7e7e542f51
commit 24cc37f220
2 changed files with 129 additions and 36 deletions

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.5)
include(cmake/color.cmake) include(cmake/color.cmake)
set(BLT_VERSION 0.14.7) set(BLT_VERSION 0.14.8)
set(BLT_TEST_VERSION 0.0.1) set(BLT_TEST_VERSION 0.0.1)
set(BLT_TARGET BLT) set(BLT_TARGET BLT)

View File

@ -540,14 +540,36 @@ namespace blt
} }
}; };
// size of 2mb in bytes
inline constexpr blt::size_t BLT_2MB_SIZE = 4096 * 512; inline constexpr blt::size_t BLT_2MB_SIZE = 4096 * 512;
/**
* blt::bump_allocator. Allocates blocks of BLOCK_SIZE with zero reuse. When all objects from a block are fully deallocated the block will be freed
* @tparam BLOCK_SIZE size of block to use. recommended to be multiple of page size or huge page size.
* @tparam USE_HUGE allocate using mmap and huge pages. If this fails it will use mmap to allocate normally. defaults to off because linux has parent huge pages.
* @tparam HUGE_PAGE_SIZE size the system allows huge pages to be. defaults to 2mb
* @tparam WARN_ON_FAIL print warning messages if allocating huge pages fail
*/
template<blt::size_t BLOCK_SIZE = BLT_2MB_SIZE, bool USE_HUGE = false, blt::size_t HUGE_PAGE_SIZE = BLT_2MB_SIZE, bool WARN_ON_FAIL = false> template<blt::size_t BLOCK_SIZE = BLT_2MB_SIZE, bool USE_HUGE = false, blt::size_t HUGE_PAGE_SIZE = BLT_2MB_SIZE, bool WARN_ON_FAIL = false>
class bump_allocator class bump_allocator
{ {
// power of two // ensure power of two
static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!"); static_assert(((BLOCK_SIZE & (BLOCK_SIZE - 1)) == 0) && "Must be a power of two!");
public:
/**
* convert any pointer back into a pointer its block
*/
template<typename T>
static inline auto to_block(T* p)
{
return reinterpret_cast<block*>(reinterpret_cast<std::uintptr_t>(p) & static_cast<std::uintptr_t>(~(BLOCK_SIZE - 1)));
}
private: private:
/**
* Logging function used for handling mmap errors. call after a failed mmap call.
* @param LOG_FUNC function to log with, must be a BLT_*_STREAM
*/
template<typename LOG_FUNC> template<typename LOG_FUNC>
static void handle_mmap_error(LOG_FUNC func = BLT_ERROR_STREAM) static void handle_mmap_error(LOG_FUNC func = BLT_ERROR_STREAM)
{ {
@ -623,11 +645,18 @@ namespace blt
} }
}; };
static constexpr blt::size_t BASE_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t); // remaining space inside the block after accounting for the metadata
static constexpr blt::size_t BLOCK_REMAINDER = BLOCK_SIZE - sizeof(typename block::block_metadata_t);
block* base = nullptr; block* base = nullptr;
block* head = nullptr; block* head = nullptr;
/**
* Handles the allocation of the bytes for the block.
* This function will either use mmap to allocate huge pages if requested
* or use std::align_alloc to create an aligned allocation
* @return pointer to a constructed block
*/
block* allocate_block() block* allocate_block()
{ {
block* buffer; block* buffer;
@ -675,23 +704,43 @@ namespace blt
return buffer; return buffer;
} }
/**
* Allocates a new block and pushes it to the front of the linked listed
*/
void allocate_forward() void allocate_forward()
{ {
auto* block = allocate_block(); auto* block = allocate_block();
if (head == nullptr)
{
base = head = block;
return;
}
block->metadata.prev = head; block->metadata.prev = head;
head->metadata.next = block; head->metadata.next = block;
head = block; head = block;
} }
/**
* handles the actual allocation and alignment of memory
* @param bytes number of bytes to allocate
* @param alignment alignment required
* @return aligned pointer
*/
void* allocate_bytes(blt::size_t bytes, blt::size_t alignment) void* allocate_bytes(blt::size_t bytes, blt::size_t alignment)
{ {
blt::size_t remaining_bytes = BASE_REMAINDER - static_cast<blt::size_t>(head->metadata.offset - head->buffer); blt::size_t remaining_bytes = BLOCK_REMAINDER - static_cast<blt::size_t>(head->metadata.offset - head->buffer);
auto pointer = static_cast<void*>(head->metadata.offset); auto pointer = static_cast<void*>(head->metadata.offset);
return std::align(alignment, bytes, pointer, remaining_bytes); return std::align(alignment, bytes, pointer, remaining_bytes);
} }
/**
* allocate an object starting from the next available address
* @tparam T type to allocate for
* @param count number of elements to allocate
* @return nullptr if the object could not be allocated, pointer to the object if it could, pointer to the start if count != 1
*/
template<typename T> template<typename T>
T* allocate_back(blt::size_t count) T* allocate_object(blt::size_t count)
{ {
blt::size_t bytes = sizeof(T) * count; blt::size_t bytes = sizeof(T) * count;
const auto aligned_address = allocate_bytes(bytes, alignof(T)); const auto aligned_address = allocate_bytes(bytes, alignof(T));
@ -703,7 +752,11 @@ namespace blt
return static_cast<T*>(aligned_address); return static_cast<T*>(aligned_address);
} }
inline void del(block* p) /**
* Frees a block
* @param p pointer to the block to free
*/
inline void delete_block(block* p)
{ {
if constexpr (USE_HUGE) if constexpr (USE_HUGE)
{ {
@ -715,63 +768,67 @@ namespace blt
} else } else
free(p); free(p);
} }
template<typename T, typename FUNC>
inline T* attempt_allocation(FUNC f, blt::size_t count)
{
T* ptr = allocate_back<T>(count);
if (ptr == nullptr)
f();
return ptr;
}
public: public:
bump_allocator() bump_allocator() = default;
{
base = head = allocate_block();
};
/** /**
* Takes an unused size parameter. Purely used for compatibility with the old bump_allocator * Takes an unused size parameter. Purely used for compatibility with the old bump_allocator
*/ */
explicit bump_allocator(blt::size_t): bump_allocator() explicit bump_allocator(blt::size_t)
{} {}
/**
* Allocate bytes for a type
* @tparam T type to allocate
* @param count number of elements to allocate for
* @throws std::bad_alloc
* @return aligned pointer to the beginning of the allocated memory
*/
template<typename T> template<typename T>
[[nodiscard]] T* allocate(blt::size_t count = 1) [[nodiscard]] T* allocate(blt::size_t count = 1)
{ {
if constexpr (sizeof(T) > BASE_REMAINDER) if constexpr (sizeof(T) > BLOCK_REMAINDER)
throw std::bad_alloc(); throw std::bad_alloc();
auto* ptr = attempt_allocation<T>([this]() { allocate_forward(); }, count); T* ptr = allocate_object<T>(count);
if (ptr != nullptr)
return ptr;
allocate_forward();
ptr = allocate_object<T>(count);
if (ptr == nullptr) if (ptr == nullptr)
return attempt_allocation<T>([]() { throw std::bad_alloc(); }, count); throw std::bad_alloc();
return ptr; return ptr;
} }
/**
* Deallocate a pointer, does not call the destructor
* @tparam T type of pointer
* @param p pointer to deallocate
*/
template<typename T> template<typename T>
void deallocate(T* p) void deallocate(T* p)
{ {
if (p == nullptr) if (p == nullptr)
return; return;
auto* blk = reinterpret_cast<block*>(reinterpret_cast<std::uintptr_t>(p) & static_cast<std::uintptr_t>(~(BLOCK_SIZE - 1))); auto blk = to_block(p);
if (--blk->metadata.allocated_objects == 0) if (--blk->metadata.allocated_objects == 0)
{ {
if (blk == base) if (blk == base)
base = allocate_block(); base = head = nullptr;
if (blk->metadata.prev != nullptr) if (blk->metadata.prev != nullptr)
blk->metadata.prev->metadata.next = blk->metadata.next; blk->metadata.prev->metadata.next = blk->metadata.next;
del(blk); delete_block(blk);
} }
} }
template<typename T> /**
auto* blk(T* p) * allocate a type then call its constructor with arguments
{ * @tparam T type to construct
return reinterpret_cast<block*>(reinterpret_cast<std::uintptr_t>(p) & static_cast<std::uintptr_t>(~(BLOCK_SIZE - 1))); * @tparam Args type of args to construct with
} * @param args args to construct with
* @return aligned pointer to the constructed type
*/
template<typename T, typename... Args> template<typename T, typename... Args>
[[nodiscard]] T* emplace(Args&& ... args) [[nodiscard]] T* emplace(Args&& ... args)
{ {
@ -779,6 +836,14 @@ namespace blt
return new(allocated_memory) T{std::forward<Args>(args)...}; return new(allocated_memory) T{std::forward<Args>(args)...};
} }
/**
* allocate an array of count T with argument(s) args and call T's constructor
* @tparam T class to construct
* @tparam Args argument types to supply to construction
* @param count size of the array to allocate in number of elements. Note calling this with count = 0 is equivalent to calling emplace
* @param args the args to supply to construction
* @return aligned pointer to the beginning of the array of T
*/
template<typename T, typename... Args> template<typename T, typename... Args>
[[nodiscard]] T* emplace_many(blt::size_t count, Args&& ... args) [[nodiscard]] T* emplace_many(blt::size_t count, Args&& ... args)
{ {
@ -790,18 +855,46 @@ namespace blt
return allocated_memory; return allocated_memory;
} }
/**
* Used to construct a class U with parameters Args
* @tparam U class to construct
* @tparam Args args to use
* @param p pointer to non-constructed memory
* @param args list of arguments to build the class with
*/
template<class U, class... Args> template<class U, class... Args>
inline void construct(U* p, Args&& ... args) inline void construct(U* p, Args&& ... args)
{ {
::new((void*) p) U(std::forward<Args>(args)...); ::new((void*) p) U(std::forward<Args>(args)...);
} }
/**
* Call the destructor for class U with pointer p
* @tparam U class to call destructor on, this will not do anything if the type is std::trivially_destructible
* @param p
*/
template<class U> template<class U>
inline void destroy(U* p) inline void destroy(U* p)
{
if constexpr (std::is_trivially_destructible_v<U>)
{ {
if (p != nullptr) if (p != nullptr)
p->~U(); p->~U();
} }
}
/**
* Calls destroy on pointer p
* Then calls deallocate on p
* @tparam U class to destroy
* @param p pointer to deallocate
*/
template<class U>
inline void destruct(U* p)
{
destroy(p);
deallocate(p);
}
~bump_allocator() ~bump_allocator()
{ {
@ -809,7 +902,7 @@ namespace blt
while (next != nullptr) while (next != nullptr)
{ {
auto* after = next->metadata.next; auto* after = next->metadata.next;
del(next); delete_block(next);
next = after; next = after;
} }
} }