docs for the allocator, minor cleanup
parent
7e7e542f51
commit
24cc37f220
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue