forked from Alepha/Alepha
Updated thread slab with overflow protection and rewritten.
This commit is contained in:
@ -434,6 +434,7 @@ namespace Alepha::Hydrogen ::detail:: Exception_m
|
|||||||
using storage_type= AllocationAmountStorage;
|
using storage_type= AllocationAmountStorage;
|
||||||
virtual ~AllocationAmountInterface()= default;
|
virtual ~AllocationAmountInterface()= default;
|
||||||
virtual std::size_t allocationAmount() const noexcept= 0;
|
virtual std::size_t allocationAmount() const noexcept= 0;
|
||||||
|
virtual void setAllocationAmount( std::size_t ) noexcept= 0;
|
||||||
};
|
};
|
||||||
class AllocationAmountStorage
|
class AllocationAmountStorage
|
||||||
: virtual public AllocationAmountInterface
|
: virtual public AllocationAmountInterface
|
||||||
@ -443,6 +444,8 @@ namespace Alepha::Hydrogen ::detail:: Exception_m
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
std::size_t allocationAmount() const noexcept final { return amount; }
|
std::size_t allocationAmount() const noexcept final { return amount; }
|
||||||
|
|
||||||
|
void setAllocationAmount( const std::size_t amount ) noexcept { this->amount= amount; }
|
||||||
};
|
};
|
||||||
class AllocationException
|
class AllocationException
|
||||||
: virtual public create_exception< struct allocation_throwable, Exception >, virtual public AllocationAmountInterface {};
|
: virtual public create_exception< struct allocation_throwable, Exception >, virtual public AllocationAmountInterface {};
|
||||||
@ -496,6 +499,7 @@ namespace Alepha::Hydrogen ::detail:: Exception_m
|
|||||||
class Undergird
|
class Undergird
|
||||||
: virtual public Kind, virtual protected GenericExceptionBridge< std::bad_alloc >,
|
: virtual public Kind, virtual protected GenericExceptionBridge< std::bad_alloc >,
|
||||||
virtual protected MessageStorage, virtual protected AllocationAmountStorage,
|
virtual protected MessageStorage, virtual protected AllocationAmountStorage,
|
||||||
|
virtual public AllocationAmountInterface,
|
||||||
virtual public std::bad_alloc
|
virtual public std::bad_alloc
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
@ -1,2 +1,6 @@
|
|||||||
add_subdirectory( Blob.test )
|
add_subdirectory( Blob.test )
|
||||||
add_subdirectory( ThreadSlab.test )
|
add_subdirectory( ThreadSlab.test )
|
||||||
|
|
||||||
|
target_sources( alepha PRIVATE
|
||||||
|
ThreadSlab.cc
|
||||||
|
)
|
||||||
|
96
Memory/ThreadSlab.cc
Normal file
96
Memory/ThreadSlab.cc
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
static_assert( __cplusplus > 2023'00 );
|
||||||
|
|
||||||
|
#include "ThreadSlab.h"
|
||||||
|
|
||||||
|
#include <Alepha/Exception.h>
|
||||||
|
|
||||||
|
namespace Alepha::Hydrogen::Memory ::detail:: ThreadSlab_m
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace C
|
||||||
|
{
|
||||||
|
const std::size_t slabSize= 64 * 1024 * 1024;
|
||||||
|
|
||||||
|
const bool debug= false;
|
||||||
|
const bool debugLifecycle= false or C::debug;
|
||||||
|
const bool debugAllocation= false or C::debug;
|
||||||
|
const bool debugDeallocation= false or C::debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename SP >
|
||||||
|
void
|
||||||
|
destroy( SP *p )
|
||||||
|
{
|
||||||
|
p->~SP();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace storage
|
||||||
|
{
|
||||||
|
thread_local Blob slab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
void *
|
||||||
|
shim::allocate( std::size_t amt )
|
||||||
|
{
|
||||||
|
// TODO: Alignment needs to be handled.
|
||||||
|
const std::size_t req= amt + sizeof( Blob::StorageReservation );
|
||||||
|
|
||||||
|
// TODO: Larger allocations may be worth bespoke allocations, if they're rare one-off cases
|
||||||
|
if( req > C::slabSize )
|
||||||
|
{
|
||||||
|
auto exc= build_exception< AllocationError >( "Unable to allocate larger than the slab size." );
|
||||||
|
//exc.setAllocationAmount( req );
|
||||||
|
throw exc;
|
||||||
|
}
|
||||||
|
if( slab().size() < req ) slab().reset( std::max( req, C::slabSize ) );
|
||||||
|
|
||||||
|
if( C::debugAllocation )
|
||||||
|
{
|
||||||
|
std::cerr << "Reporting " << slab().reservation().use_count() << " living allocations when "
|
||||||
|
<< (void *) &slab << " made an allocation." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto next= slab().carveHead( req );
|
||||||
|
void *const rv= &next.as< Blob::StorageReservation >() + 1;
|
||||||
|
|
||||||
|
// FIXME: The placement new here is potentially unaligned -- this may significantly impact
|
||||||
|
// performance. It is also non-portable.
|
||||||
|
new ( &next.template as< Blob::StorageReservation >() ) Blob::StorageReservation{ std::move( next.reservation() ) };
|
||||||
|
|
||||||
|
if( C::debugAllocation )
|
||||||
|
{
|
||||||
|
std::cerr << "Reporting " << slab().reservation().use_count() << " living allocations when "
|
||||||
|
<< (void *) &slab() << " made an allocation." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
shim::deallocate( void *p ) noexcept
|
||||||
|
{
|
||||||
|
if( C::debugDeallocation )
|
||||||
|
{
|
||||||
|
std::cerr << "Reporting " << slab().reservation().use_count() << " living allocations when "
|
||||||
|
<< (void *) &slab() << " made a deallocation." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *const hidden= reinterpret_cast< Blob::StorageReservation * >( p ) - 1;
|
||||||
|
destroy( hidden );
|
||||||
|
|
||||||
|
if( C::debugDeallocation )
|
||||||
|
{
|
||||||
|
std::cerr << "Reporting " << slab().reservation().use_count() << " living allocations when "
|
||||||
|
<< (void *) &slab() << " made a deallocation." << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Blob &
|
||||||
|
shim::slab()
|
||||||
|
{
|
||||||
|
return storage::slab;
|
||||||
|
}
|
||||||
|
}
|
@ -5,35 +5,58 @@ static_assert( __cplusplus > 2020'99 );
|
|||||||
#include <Alepha/Alepha.h>
|
#include <Alepha/Alepha.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include <Alepha/Memory/Blob.h>
|
#include <Alepha/Memory/Blob.h>
|
||||||
|
|
||||||
namespace Alepha::Hydrogen::Memory ::detail:: ThreadSlab_m
|
namespace Alepha::Hydrogen::Memory ::detail:: ThreadSlab_m
|
||||||
{
|
{
|
||||||
inline namespace exports
|
inline namespace exports {}
|
||||||
|
|
||||||
|
namespace exports::inline ThreadSlab
|
||||||
{
|
{
|
||||||
template< typename T >
|
template< typename T >
|
||||||
class ThreadSlab;
|
class Allocator;
|
||||||
|
|
||||||
using ThreadSlabString= std::basic_string< char, std::char_traits< char >, ThreadSlab< char > >;
|
using String= std::basic_string< char, std::char_traits< char >, Allocator< char > >;
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
using Vector= std::vector< T, Allocator< T > >;
|
||||||
|
|
||||||
|
template< typename K, typename V, typename Compare= std::less< K > >
|
||||||
|
using Map= std::map< K, V, Allocator< std::pair< const K, V > > >;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace C
|
namespace shim
|
||||||
{
|
{
|
||||||
const std::size_t slabSize= 64 * 1024 * 1024;
|
[[nodiscard]] void *allocate( std::size_t );
|
||||||
|
void deallocate( void * ) noexcept;
|
||||||
|
|
||||||
const bool debug= false;
|
Blob &slab();
|
||||||
const bool debugLifecycle= false or C::debug;
|
|
||||||
const bool debugAllocation= false or C::debug;
|
|
||||||
const bool debugDeallocation= false or C::debug;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template< typename T >
|
template< typename T >
|
||||||
class exports::ThreadSlab
|
struct BlockFromSlab
|
||||||
{
|
{
|
||||||
public:
|
std::byte raw[ sizeof( T ) ];
|
||||||
inline static thread_local Blob slab;
|
|
||||||
|
|
||||||
|
[[nodiscard]] static void *
|
||||||
|
operator new [] ( const std::size_t sz )
|
||||||
|
{
|
||||||
|
return shim::allocate( sz );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
operator delete [] ( void *const p ) noexcept
|
||||||
|
{
|
||||||
|
return shim::deallocate( p );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
class exports::Allocator
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
using value_type= T;
|
using value_type= T;
|
||||||
using propagate_on_container_copy_assignment= std::true_type;
|
using propagate_on_container_copy_assignment= std::true_type;
|
||||||
@ -41,85 +64,34 @@ namespace Alepha::Hydrogen::Memory ::detail:: ThreadSlab_m
|
|||||||
using propagate_on_container_swap= std::true_type;
|
using propagate_on_container_swap= std::true_type;
|
||||||
using is_always_equal= std::true_type;
|
using is_always_equal= std::true_type;
|
||||||
|
|
||||||
ThreadSlab select_on_container_copy_construction() { auto rv= ThreadSlab{}; }
|
Allocator select_on_container_copy_construction() { auto rv= Allocator{}; }
|
||||||
|
|
||||||
ThreadSlab()= default;
|
Allocator()= default;
|
||||||
|
|
||||||
|
|
||||||
ThreadSlab &operator= ( const ThreadSlab &other )= default;
|
Allocator &operator= ( const Allocator &other )= default;
|
||||||
|
|
||||||
ThreadSlab( const ThreadSlab &other )= default;
|
Allocator( const Allocator &other )= default;
|
||||||
|
|
||||||
ThreadSlab( ThreadSlab &&other ) : ThreadSlab( std::as_const( other ) ) {}
|
Allocator( Allocator &&other ) : Allocator( std::as_const( other ) ) {}
|
||||||
|
|
||||||
~ThreadSlab()
|
~Allocator()= default;
|
||||||
{
|
|
||||||
if( C::debugLifecycle )
|
static Blob &slab() { return shim::slab(); }
|
||||||
{
|
|
||||||
std::cerr << "Reporting " << slab.reservation().use_count() << " living allocations when "
|
|
||||||
<< (void *) this << " is retired." << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] T *
|
[[nodiscard]] T *
|
||||||
allocate( const std::size_t amt )
|
allocate( const std::size_t amt )
|
||||||
{
|
{
|
||||||
// TODO: Alignment needs to be handled.
|
return reinterpret_cast< T * >( new BlockFromSlab< T >[ amt ] );
|
||||||
const std::size_t req= amt + sizeof( Blob::StorageReservation );
|
|
||||||
|
|
||||||
// TODO: Larger allocations may be worth bespoke allocations, if they're rare one-off cases
|
|
||||||
if( req > C::slabSize ) throw std::bad_alloc{}; //{ "Unable to allocate larger than the slab size." };
|
|
||||||
if( slab.size() < req ) slab.reset( std::max( req, C::slabSize ) );
|
|
||||||
|
|
||||||
if( C::debugAllocation )
|
|
||||||
{
|
|
||||||
std::cerr << "Reporting " << slab.reservation().use_count() << " living allocations when "
|
|
||||||
<< (void *) this << " made an allocation." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto next= slab.carveHead( req + sizeof( Blob::StorageReservation ) );
|
|
||||||
const auto rv= reinterpret_cast< T * >( &next.template as< Blob::StorageReservation >() + 1 );
|
|
||||||
|
|
||||||
// FIXME: The placement new here is potentially unaligned -- this may significantly impact
|
|
||||||
// performance.
|
|
||||||
new ( &next.template as< Blob::StorageReservation >() ) Blob::StorageReservation{ std::move( next.reservation() ) };
|
|
||||||
|
|
||||||
if( C::debugAllocation )
|
|
||||||
{
|
|
||||||
std::cerr << "Reporting " << slab.reservation().use_count() << " living allocations when "
|
|
||||||
<< (void *) this << " made an allocation." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
|
||||||
}
|
|
||||||
|
|
||||||
template< typename SP >
|
|
||||||
static void
|
|
||||||
destroy( SP *p )
|
|
||||||
{
|
|
||||||
p->~SP();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
deallocate( T *const p, const std::size_t /* ignored */ ) noexcept
|
deallocate( T *const p, const std::size_t /* ignored */ ) noexcept
|
||||||
{
|
{
|
||||||
if( C::debugDeallocation )
|
return BlockFromSlab< T >::operator delete [] ( p );
|
||||||
{
|
|
||||||
std::cerr << "Reporting " << slab.reservation().use_count() << " living allocations when "
|
|
||||||
<< (void *) this << " made a deallocation." << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *const hidden= reinterpret_cast< Blob::StorageReservation * >( p ) - 1;
|
|
||||||
destroy( hidden );
|
|
||||||
|
|
||||||
if( C::debugDeallocation )
|
|
||||||
{
|
|
||||||
std::cerr << "Reporting " << slab.reservation().use_count() << " living allocations when "
|
|
||||||
<< (void *) this << " made a deallocation." << std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
friend constexpr bool operator == ( const ThreadSlab &, const ThreadSlab & ) noexcept { return true; }
|
friend constexpr bool operator == ( const Allocator &, const Allocator & ) noexcept { return true; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,12 +11,12 @@ static auto init= Alepha::Utility::enroll <=[]
|
|||||||
using namespace Alepha::Testing::literals;
|
using namespace Alepha::Testing::literals;
|
||||||
|
|
||||||
using namespace Alepha::Memory::exports::ThreadSlab_m;
|
using namespace Alepha::Memory::exports::ThreadSlab_m;
|
||||||
using String= ThreadSlabString;
|
using String= ThreadSlab::String;
|
||||||
|
|
||||||
|
|
||||||
"Check slab usage"_test <=[]
|
"Check slab usage"_test <=[]
|
||||||
{
|
{
|
||||||
std::cout << "I see " << Alepha::Memory::ThreadSlab< char >::slab.reservation().use_count() << " reservations in a separate test." <<
|
std::cout << "I see " << Alepha::Memory::detail::ThreadSlab_m::shim::slab().reservation().use_count() << " reservations in a separate test." <<
|
||||||
std::endl;
|
std::endl;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ std::endl;
|
|||||||
|
|
||||||
"Check slab usage"_test <=[]
|
"Check slab usage"_test <=[]
|
||||||
{
|
{
|
||||||
std::cout << "I see " << Alepha::Memory::ThreadSlab< char >::slab.reservation().use_count() << " reservations in a separate test." <<
|
std::cout << "I see " << Alepha::Memory::detail::ThreadSlab_m::shim::slab().reservation().use_count() << " reservations in a separate test." <<
|
||||||
std::endl;
|
std::endl;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user