diff --git a/Memory/Blob.h b/Memory/Blob.h index c24cd6b..5123be8 100644 --- a/Memory/Blob.h +++ b/Memory/Blob.h @@ -17,6 +17,8 @@ static_assert( __cplusplus > 2020'99 ); #include "Buffer.h" +// TODO: Put this into the `Alepha::Memory::` namespace. +// TODO: Consider whether a "republish" into `Alepha::` namespace is a good idea. namespace Alepha::Hydrogen ::detail:: Blob_m { inline namespace exports @@ -94,6 +96,9 @@ namespace Alepha::Hydrogen ::detail:: Blob_m public: ~Blob() { reset(); } + using StorageReservation= IndirectStorage; + const StorageReservation &reservation() const { return storage; } + auto swap_lens() noexcept { @@ -218,7 +223,10 @@ namespace Alepha::Hydrogen ::detail:: Blob_m * inside a large single physical backing. This helps maintain zero-copy semantics. * * @param amount The amount of data to carve off. - * @return A new `Blob` object referring to the same physical data, scoped to `amount` bytes. + * @param alignment The size alignment that the new base should be at (the extra padding is + * considered part of the resulting `Blob`). + * @return A new `Blob` object referring to the same physical data, scoped to `amount` bytes (with + * possible extra space, due to alignment). */ Blob carveHead( const std::size_t amount ) diff --git a/Memory/CMakeLists.txt b/Memory/CMakeLists.txt index 0964a08..cb3030b 100644 --- a/Memory/CMakeLists.txt +++ b/Memory/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory( Blob.test ) +add_subdirectory( ThreadSlab.test ) diff --git a/Memory/DataChain.h b/Memory/DataChain.h index 6f33edd..0f5853e 100644 --- a/Memory/DataChain.h +++ b/Memory/DataChain.h @@ -52,7 +52,7 @@ namespace Alepha::inline Cavorite ::detail:: DataChain_m friend DataChain; - explicit Iterator( const ChainIter pos, cosnt std::size_t offset ) noexcept : pos( pos ), offset( offset ) {} + explicit Iterator( const ChainIter pos, const std::size_t offset ) noexcept : pos( pos ), offset( offset ) {} public: auto @@ -165,7 +165,7 @@ namespace Alepha::inline Cavorite ::detail:: DataChain_m std::copy_n( std::prev( end(), amount ), amount, rv.byte_data() ); return rv; - + } }; }; } diff --git a/Memory/ThreadSlab.h b/Memory/ThreadSlab.h new file mode 100644 index 0000000..3b12023 --- /dev/null +++ b/Memory/ThreadSlab.h @@ -0,0 +1,129 @@ +static_assert( __cplusplus > 2020'99 ); + +#pragma once + +#include + +#include + +#include + +namespace Alepha::Hydrogen::Memory ::detail:: ThreadSlab_m +{ + inline namespace exports + { + template< typename T > + class ThreadSlab; + + using ThreadSlabString= std::basic_string< char, std::char_traits< char >, ThreadSlab< char > >; + } + + 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 T > + class exports::ThreadSlab + { + public: + inline static thread_local Blob slab; + + public: + using value_type= T; + using propagate_on_container_copy_assignment= std::true_type; + using propagate_on_container_move_assignment= std::true_type; + using propagate_on_container_swap= std::true_type; + using is_always_equal= std::true_type; + + ThreadSlab select_on_container_copy_construction() { auto rv= ThreadSlab{}; } + + ThreadSlab()= default; + + + ThreadSlab &operator= ( const ThreadSlab &other )= default; + + ThreadSlab( const ThreadSlab &other )= default; + + ThreadSlab( ThreadSlab &&other ) : ThreadSlab( std::as_const( other ) ) {} + + ~ThreadSlab() + { + if( C::debugLifecycle ) + { + std::cerr << "Reporting " << slab.reservation().use_count() << " living allocations when " + << (void *) this << " is retired." << std::endl; + } + } + + [[nodiscard]] T * + allocate( const 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 ) 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 + deallocate( T *const p, const std::size_t /* ignored */ ) noexcept + { + if( C::debugDeallocation ) + { + 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; } + }; +} + +namespace Alepha::Hydrogen::Memory::inline exports::inline ThreadSlab_m +{ + using namespace detail::ThreadSlab_m::exports; +} diff --git a/Memory/ThreadSlab.test/0.cc b/Memory/ThreadSlab.test/0.cc new file mode 100644 index 0000000..675b9f8 --- /dev/null +++ b/Memory/ThreadSlab.test/0.cc @@ -0,0 +1,49 @@ +static_assert( __cplusplus > 2020'99 ); + +#include "../ThreadSlab.h" + +#include + +#include + +static auto init= Alepha::Utility::enroll <=[] +{ + using namespace Alepha::Testing::literals; + + using namespace Alepha::Memory::exports::ThreadSlab_m; + using String= ThreadSlabString; + + + "Check slab usage"_test <=[] + { + std::cout << "I see " << Alepha::Memory::ThreadSlab< char >::slab.reservation().use_count() << " reservations in a separate test." << +std::endl; + }; + + "Can we work with simple `ThreadSlabStrings` without errors?"_test <=[] + { + String s; + std::cerr << "s is empty" << std::endl; + + String s2= "Hello World"; + std::cerr << "small hello world string." << std::endl; + + String s3= s2 + ": and bob"; + + for( int i= 0; i < 10; ++i ) + { + std::cerr << "appended..." << std::endl; + s3= s3 + s3 + s2; + + s2= std::move( s3 ); + } + + std::cout << s3 << std::endl; + }; + + "Check slab usage"_test <=[] + { + std::cout << "I see " << Alepha::Memory::ThreadSlab< char >::slab.reservation().use_count() << " reservations in a separate test." << +std::endl; + }; +}; diff --git a/Memory/ThreadSlab.test/CMakeLists.txt b/Memory/ThreadSlab.test/CMakeLists.txt new file mode 100644 index 0000000..b099603 --- /dev/null +++ b/Memory/ThreadSlab.test/CMakeLists.txt @@ -0,0 +1 @@ +unit_test( 0 )