240 lines
6.5 KiB
C++
240 lines
6.5 KiB
C++
/******************************************************************************/
|
|
/* ALLOCATORS *****************************************************************/
|
|
/******************************************************************************/
|
|
|
|
#ifndef _H_EBCL_ALLOC
|
|
#define _H_EBCL_ALLOC
|
|
#include <ebcl/Config.hh>
|
|
#include <ebcl/Utilities.hh>
|
|
#include <atomic>
|
|
namespace ebcl {
|
|
|
|
|
|
/* Helpers for pool allocators. */
|
|
struct T_PoolHelper
|
|
{
|
|
/* Storage type for an object */
|
|
template<
|
|
size_t ObjectSize ,
|
|
size_t ObjectAlign
|
|
> using T_Object = std::aligned_storage_t< ObjectSize , ObjectAlign >;
|
|
|
|
/* List of object storage items. The storage type is assumed to
|
|
* have a void* field called list.
|
|
*/
|
|
template<
|
|
typename T_Storage ,
|
|
size_t ListSize
|
|
> struct T_List final
|
|
{
|
|
static constexpr size_t BitmapSize
|
|
= ( ListSize / 8 ) + ( ListSize % 8 ? 1 : 0 );
|
|
|
|
using T_Self = T_List< T_Storage , ListSize >;
|
|
|
|
T_Storage storage[ ListSize ]; // Stored data
|
|
T_Self* next; // Next list in chain
|
|
size_t free; // Free objects
|
|
uint8_t bitmap[ BitmapSize ]; // Allocation map
|
|
|
|
T_List( ) = delete;
|
|
T_List( T_Self const& ) = delete;
|
|
T_List( T_Self&& ) = delete;
|
|
|
|
// Create a new list, replacing the chain's head
|
|
explicit T_List( T_Self*& head ) noexcept;
|
|
|
|
// Find an unused item in the list. The list must have free
|
|
// items.
|
|
T_Storage* findUnused( ) noexcept;
|
|
// Find the index of an item. The item must be part of the list.
|
|
size_t indexOf( T_Storage const* item ) noexcept;
|
|
|
|
// Destroy a complete chain of lists
|
|
static void destroy( T_Self*& head ) noexcept;
|
|
};
|
|
|
|
/* The core of a pool allocator */
|
|
template<
|
|
typename T_Storage ,
|
|
size_t PerList ,
|
|
size_t MaxFreeLists
|
|
> struct T_Allocator final
|
|
{
|
|
static_assert( PerList > 1 , "allocator lists must contain at least 2 items" );
|
|
static_assert( MaxFreeLists > 0 , "allocator must have at least 1 free list" );
|
|
|
|
using T_List_ = T_List< T_Storage , PerList >;
|
|
using T_Self = T_Allocator< T_Storage , PerList , MaxFreeLists >;
|
|
|
|
// The chains
|
|
T_List_* free_;
|
|
T_List_* partial_;
|
|
T_List_* full_;
|
|
size_t nFree_;
|
|
|
|
T_Allocator( ) noexcept;
|
|
~T_Allocator( );
|
|
|
|
T_Allocator( T_Self const& ) = delete;
|
|
T_Allocator( T_Self&& ) = delete;
|
|
|
|
/* Allocate an object. */
|
|
T_Storage* allocate( ) noexcept;
|
|
/* Free an object */
|
|
void free( T_Storage* item ) noexcept;
|
|
|
|
/* Get the amount of lists. Mostly used for testing. */
|
|
size_t countFreeLists( ) const noexcept;
|
|
size_t countPartialLists( ) const noexcept;
|
|
size_t countFullLists( ) const noexcept;
|
|
|
|
/* Get the amount of total, free and used objects */
|
|
void getUsage( size_t& total ,
|
|
size_t& free ,
|
|
size_t& used ) const noexcept;
|
|
|
|
private:
|
|
/* Move a list of objects from one of the allocation lists to another. */
|
|
static void moveList(
|
|
T_List_* list ,
|
|
T_List_*& from ,
|
|
T_List_*& to ) noexcept;
|
|
|
|
/* Count the amount of lists in an allocation list */
|
|
static size_t countLists(
|
|
T_List_ const* head ) noexcept;
|
|
};
|
|
};
|
|
|
|
/*============================================================================*/
|
|
|
|
/* The pool allocator implements a thread-unsafe pool of objects, similar to a
|
|
* slab allocator.
|
|
*/
|
|
template<
|
|
size_t ObjectSize , size_t ObjectAlign ,
|
|
size_t PerList = std::max( size_t( 8 ) , 4096u / ObjectSize ) ,
|
|
size_t MaxFreeLists = 4
|
|
>
|
|
class T_PoolAllocator
|
|
{
|
|
public:
|
|
using T_Self = T_PoolAllocator< ObjectSize , ObjectAlign , PerList , MaxFreeLists >;
|
|
|
|
private:
|
|
// Structure that stores a single allocated item
|
|
struct T_Storage_ final
|
|
{
|
|
T_PoolHelper::T_Object< ObjectSize , ObjectAlign > data;
|
|
void* list;
|
|
};
|
|
|
|
using T_Alloc_ = T_PoolHelper::T_Allocator< T_Storage_ , PerList , MaxFreeLists >;
|
|
T_Alloc_ alloc_;
|
|
|
|
public:
|
|
T_PoolAllocator( ) noexcept = default;
|
|
T_PoolAllocator( T_Self const& ) = delete;
|
|
T_PoolAllocator( T_Self&& ) = delete;
|
|
|
|
/* Allocate an object. */
|
|
void* allocate( size_t requested ) noexcept;
|
|
/* Free an object */
|
|
void free( void* item ) noexcept;
|
|
|
|
/* Get the amount of lists. Mostly used for testing. */
|
|
size_t countFreeLists( ) const noexcept;
|
|
size_t countPartialLists( ) const noexcept;
|
|
size_t countFullLists( ) const noexcept;
|
|
|
|
/* Get the amount of total, free and used objects */
|
|
void getUsage( size_t& total ,
|
|
size_t& free ,
|
|
size_t& used ) const noexcept;
|
|
};
|
|
|
|
/*============================================================================*/
|
|
|
|
/* Pool allocator with multithreading support. Allocation is performed from
|
|
* a thread-specific pool allocator; when the memory is freed, the memory is
|
|
* either returned directly to the pool if the freeing thread is the same as
|
|
* the allocating thread, or queued for the original thread to free later.
|
|
*/
|
|
template<
|
|
size_t ObjectSize , size_t ObjectAlign ,
|
|
size_t PerList = std::max( size_t( 8 ) , 4096u / ObjectSize ) ,
|
|
size_t MaxFreeLists = 4
|
|
>
|
|
class T_ThreadedPoolAllocator
|
|
{
|
|
public:
|
|
using T_Self = T_ThreadedPoolAllocator<
|
|
ObjectSize , ObjectAlign , PerList , MaxFreeLists >;
|
|
|
|
private:
|
|
// Pointer to either the owning pool or the next object in the
|
|
// pending list
|
|
struct T_Storage_;
|
|
union T_ExtraPointer_
|
|
{
|
|
T_Storage_* next;
|
|
T_Self* pool;
|
|
};
|
|
|
|
// Structure that stores a single allocated item
|
|
struct T_Storage_ final
|
|
{
|
|
T_PoolHelper::T_Object< ObjectSize , ObjectAlign > data;
|
|
void* list;
|
|
T_ExtraPointer_ extra;
|
|
};
|
|
|
|
// The head for the "remote" freeing stack
|
|
struct T_FreeHead_ final
|
|
{
|
|
uintptr_t aba{ 0 };
|
|
T_Storage_* head{ nullptr };
|
|
};
|
|
|
|
// Atomic stack head, plus padding to prevent false sharing between
|
|
// the stack head and the actual allocator.
|
|
char padding_0_[ 64 ];
|
|
std::atomic< T_FreeHead_ > freeHead_;
|
|
char padding_1_[ 64 - sizeof( std::atomic< T_FreeHead_ > ) ];
|
|
|
|
using T_Alloc_ = T_PoolHelper::T_Allocator<
|
|
T_Storage_ , PerList , MaxFreeLists >;
|
|
T_Alloc_ alloc_;
|
|
|
|
public:
|
|
T_ThreadedPoolAllocator( ) noexcept = default;
|
|
T_ThreadedPoolAllocator( T_Self const& ) = delete;
|
|
T_ThreadedPoolAllocator( T_Self&& ) = delete;
|
|
|
|
/* Allocate an object. */
|
|
void* allocate( size_t requested ) noexcept;
|
|
/* Free an object */
|
|
void free( void* item ) noexcept;
|
|
|
|
/* Get the amount of lists. Mostly used for testing. */
|
|
size_t countFreeLists( ) const noexcept;
|
|
size_t countPartialLists( ) const noexcept;
|
|
size_t countFullLists( ) const noexcept;
|
|
|
|
/* Get the amount of total, free and used objects */
|
|
void getUsage( size_t& total ,
|
|
size_t& free ,
|
|
size_t& used ) const noexcept;
|
|
|
|
private:
|
|
// Try to take an item from the stack.
|
|
T_Storage_* takeFromStack( ) noexcept;
|
|
// Add an item to the stack
|
|
void addToStack( T_Storage_* item ) noexcept;
|
|
};
|
|
|
|
|
|
}
|
|
#endif //_H_EBCL_ALLOC
|
|
#include <ebcl/inline/Alloc.hh>
|