From 2b897da0abf443d002905851bd1efdbb7b269cfd Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Sat, 11 May 2024 18:38:32 -0400 Subject: [PATCH] Recording how I _want_ it to work. It looks like the move operation on allocators is actually a copy! --- CMakeLists.txt | 1 + Memory/CMakeLists.txt | 1 + Memory/ThreadSlab.h | 151 ++++++++++++++++++++++++++ Memory/ThreadSlab.test/0.cc | 32 ++++++ Memory/ThreadSlab.test/CMakeLists.txt | 1 + 5 files changed, 186 insertions(+) create mode 100644 Memory/CMakeLists.txt create mode 100644 Memory/ThreadSlab.h create mode 100644 Memory/ThreadSlab.test/0.cc create mode 100644 Memory/ThreadSlab.test/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 88e74ce..6e8ac9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ link_libraries( alepha ) add_subdirectory( Meta ) add_subdirectory( Atomic ) add_subdirectory( Proof ) +add_subdirectory( Memory ) add_subdirectory( IOStreams ) add_subdirectory( Reflection ) add_subdirectory( Algorithm ) diff --git a/Memory/CMakeLists.txt b/Memory/CMakeLists.txt new file mode 100644 index 0000000..c1e62f0 --- /dev/null +++ b/Memory/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory( ThreadSlab.test ) diff --git a/Memory/ThreadSlab.h b/Memory/ThreadSlab.h new file mode 100644 index 0000000..21e943f --- /dev/null +++ b/Memory/ThreadSlab.h @@ -0,0 +1,151 @@ +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; + } + + template< typename T > + class exports::ThreadSlab + { + private: + class Impl + { + public: + std::vector< Blob > leases; + + inline static thread_local Blob slab; + + Impl() { leases.reserve( 8 ); } + }; + + std::shared_ptr< Impl > pimpl; + + 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::false_type; + + ThreadSlab select_on_container_copy_construction() { auto rv= ThreadSlab{}; std::cerr << "selection creation at " << (void *) &rv << std::endl; return rv; } + + ThreadSlab() + : pimpl( std::make_unique< Impl >() ) + { + std::cerr << (void *) this << " owns " << (void *) pimpl.get() << " (creation)" << std::endl; + } + + ~ThreadSlab() { std::cerr << "Retired allocator external " << (void *) this << " which held " << (void *) pimpl.get() << std::endl; } + + ThreadSlab & + operator= ( ThreadSlab &&other ) + { + std::cerr << "Allocator move assign from " << (void *) &other << " which owns " << (void *) other.pimpl.get() + << " to " << (void *) this << " which owns " << (void *) pimpl.get() << std::endl; + + pimpl= std::move( other.pimpl ); + other.pimpl.reset(); + std::cerr << "After the move assign from " << (void *) &other << " it now owns " << (void *) other.pimpl.get() + << " and I'm still " << (void *) this << " which now owns " << (void *) pimpl.get() << std::endl; + + return *this; + } + + ThreadSlab & + operator= ( const ThreadSlab &other ) + { + std::cerr << "Allocator copy assign from " << (void *) &other << " which owns " << (void *) other.pimpl.get() + << " to " << (void *) this << " which owns " << (void *) pimpl.get() << std::endl; + + pimpl= other.pimpl; + std::cerr << "After the copy assign from " << (void *) &other << " it now owns " << (void *) other.pimpl.get() + << " and I'm still " << (void *) this << " which now owns " << (void *) pimpl.get() << std::endl; + + return *this; + } + + ThreadSlab( const ThreadSlab &other ) + : pimpl( other.pimpl ) + { + std::cerr << "Allocator copy construction created " << (void *) this << " (from " << (void *) &other << ") which held " << (void *) pimpl.get() << std::endl; + } + + ThreadSlab( ThreadSlab &&other ) + : pimpl( std::move( other.pimpl ) ) + { + std::cerr << "Allocator move construction created " << (void *) this << " (from " << (void *) &other << ") which held " << (void *) pimpl.get() << std::endl; + assert( pimpl ); + std::cerr << "Allocator move construction still holds " << pimpl.get() << std::endl; + } + + [[nodiscard]] T * + allocate( const std::size_t amt ) + { + assert( pimpl ); + std::cerr << (void *) this << " owns " << (void *) pimpl.get() << " (alloc)" << std::endl; + if( pimpl->slab.size() < amt ) pimpl->slab.reset( std::max( amt, C::slabSize ) ); + + auto next= pimpl->slab.carveHead( amt ); + const auto rv= &next.template as< T >(); + std::cerr << "Carved off " << amt << " bytes to address " << (void *) rv << " as allocator at " << (void *) pimpl.get() << std::endl; + + pimpl->leases.push_back( std::move( next ) ); + + std::cerr << "The allocator at 0x" << (void *) pimpl.get() << " has " << pimpl->leases.size() << " leases. (alloc)" << std::endl; + assert( pimpl ); + + return rv; + } + + void + deallocate( T *const p, const std::size_t /* ignored */ ) noexcept + { + assert( pimpl ); + std::cerr << (void *) this << " owns " << (void *) pimpl.get() << " (dealloc)" << std::endl; + std::cerr << "Deallocation attempt of " << (void *) p << " by " << (void *) pimpl.get() << " when there are " << pimpl->leases.size() << " leases left." << std::endl; + auto found= std::find_if( begin( pimpl->leases ), end( pimpl->leases ), [p]( const auto &x ) { return x.data() == p; } ); + if( found != end( pimpl->leases ) ) + { + pimpl->leases.erase( found ); + std::cerr << "The allocator at 0x" << (void *) pimpl.get() << " has " << pimpl->leases.size() << " leases. (dealloc)" << std::endl; + } + else + { + abort(); // Panic because it wasn't found in the list! + } + assert( pimpl ); + } + + friend constexpr bool + operator == ( const ThreadSlab &a, const ThreadSlab &b ) noexcept + { + assert( a.pimpl ); + assert( b.pimpl ); + return a.pimpl == b.pimpl; + } + }; +} + +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..4edfc34 --- /dev/null +++ b/Memory/ThreadSlab.test/0.cc @@ -0,0 +1,32 @@ +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; + + "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"; + std::cerr << "appended..." << std::endl; + s3= s3 + s3 + s2; + + s2= std::move( s3 ); + + std::cout << s3 << 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 )