1
0
forked from Alepha/Alepha

Updated thread slab with overflow protection and rewritten.

This commit is contained in:
2024-09-06 17:06:08 -04:00
parent 9717ae49a4
commit 5efc8b79f0
5 changed files with 153 additions and 77 deletions

View File

@ -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
{}; {};

View File

@ -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
View 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;
}
}

View File

@ -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; friend constexpr bool operator == ( const Allocator &, const Allocator & ) noexcept { return true; }
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; }
}; };
} }

View File

@ -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;
}; };
}; };