forked from Alepha/Alepha
Blob based per-thread slab allocator
Uses a fast random generator to decide when to split Blobs. Closes: #31
This commit is contained in:
@ -12,6 +12,7 @@ add_library( alepha SHARED
|
|||||||
Console.cc
|
Console.cc
|
||||||
ProgramOptions.cc
|
ProgramOptions.cc
|
||||||
string_algorithms.cc
|
string_algorithms.cc
|
||||||
|
fastRandom.cc
|
||||||
word_wrap.cc
|
word_wrap.cc
|
||||||
Thread.cc
|
Thread.cc
|
||||||
delimited_list.cc
|
delimited_list.cc
|
||||||
|
@ -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
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
@ -5,10 +5,12 @@ static_assert( __cplusplus > 2020'99 );
|
|||||||
#include <Alepha/Alepha.h>
|
#include <Alepha/Alepha.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <Alepha/swappable.h>
|
#include <Alepha/swappable.h>
|
||||||
|
#include <Alepha/fastRandom.h>
|
||||||
#include <Alepha/Exception.h>
|
#include <Alepha/Exception.h>
|
||||||
#include <Alepha/error.h>
|
#include <Alepha/error.h>
|
||||||
|
|
||||||
@ -17,6 +19,8 @@ static_assert( __cplusplus > 2020'99 );
|
|||||||
|
|
||||||
#include "Buffer.h"
|
#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
|
namespace Alepha::Hydrogen ::detail:: Blob_m
|
||||||
{
|
{
|
||||||
inline namespace exports
|
inline namespace exports
|
||||||
@ -28,6 +32,9 @@ namespace Alepha::Hydrogen ::detail:: Blob_m
|
|||||||
|
|
||||||
namespace C
|
namespace C
|
||||||
{
|
{
|
||||||
|
const auto doubleBlobPointerOption= "ALEPHA_USE_DOUBLE_BLOB_POINTERS";
|
||||||
|
const int storageSplitRandomBitDepth= 12; // TODO: Environment tunable? Global tunable?
|
||||||
|
|
||||||
const bool debug= false;
|
const bool debug= false;
|
||||||
const bool debugLifecycle= false or C::debug;
|
const bool debugLifecycle= false or C::debug;
|
||||||
const bool debugCtors= false or C::debugLifecycle or C::debug;
|
const bool debugCtors= false or C::debugLifecycle or C::debug;
|
||||||
@ -75,25 +82,37 @@ namespace Alepha::Hydrogen ::detail:: Blob_m
|
|||||||
Buffer< Mutable > buffer;
|
Buffer< Mutable > buffer;
|
||||||
std::size_t viewLimit= 0; // TODO: Consider allowing for unrooted sub-buffer views?
|
std::size_t viewLimit= 0; // TODO: Consider allowing for unrooted sub-buffer views?
|
||||||
|
|
||||||
// TODO: Take the `storage` parameter and make it not increment when this ctor is called -- only when the dice roll passes.
|
// Potentially rollover the internal storage, during a blob sharing operation.
|
||||||
|
void
|
||||||
|
maybeRollover()
|
||||||
|
{
|
||||||
|
if( not ::getenv( C::doubleBlobPointerOption ) ) return;
|
||||||
|
|
||||||
|
if( Alepha::fastRandomBits( C::storageSplitRandomBitDepth ) ) return;
|
||||||
|
|
||||||
|
// The inner pointer gets incremented.
|
||||||
|
std::shared_ptr< Blob > inner= *storage;
|
||||||
|
|
||||||
|
// Capture the new inner into the outer.
|
||||||
|
storage= std::make_shared< std::shared_ptr< Blob > >( std::move( inner ) );
|
||||||
|
|
||||||
|
// Post rollover, there should only be one reference to start.
|
||||||
|
assert( storage.use_count() == 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Takeover a portion of a shared block
|
||||||
explicit
|
explicit
|
||||||
Blob( IndirectStorage storage, Buffer< Mutable > buffer ) noexcept
|
Blob( IndirectStorage storage, Buffer< Mutable > buffer ) noexcept
|
||||||
: storage( Utility::evaluate <=[storage= std::move( storage )] () -> IndirectStorage
|
: storage( std::move( storage ) ), buffer( buffer ), viewLimit( buffer.size() )
|
||||||
{
|
|
||||||
//if( fastRandomBits( C::storageSplitRandomBitDepth ) )
|
|
||||||
return std::move( storage );
|
|
||||||
//if( C::debugSplitSharing ) error() << "Observed a use count of " << storage.use_count() << " when we failed the dice roll." << std::endl;
|
|
||||||
//auto split= std::make_shared< std::shared_ptr< Blob > >( *storage );
|
|
||||||
//if( C::
|
|
||||||
//return split;
|
|
||||||
}),
|
|
||||||
buffer( buffer ),
|
|
||||||
viewLimit( buffer.size() )
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~Blob() { reset(); }
|
~Blob() { reset(); }
|
||||||
|
|
||||||
|
using StorageReservation= IndirectStorage;
|
||||||
|
const StorageReservation &reservation() const { return storage; }
|
||||||
|
|
||||||
auto
|
auto
|
||||||
swap_lens() noexcept
|
swap_lens() noexcept
|
||||||
{
|
{
|
||||||
@ -218,7 +237,10 @@ namespace Alepha::Hydrogen ::detail:: Blob_m
|
|||||||
* inside a large single physical backing. This helps maintain zero-copy semantics.
|
* inside a large single physical backing. This helps maintain zero-copy semantics.
|
||||||
*
|
*
|
||||||
* @param amount The amount of data to carve off.
|
* @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
|
Blob
|
||||||
carveHead( const std::size_t amount )
|
carveHead( const std::size_t amount )
|
||||||
@ -234,7 +256,10 @@ namespace Alepha::Hydrogen ::detail:: Blob_m
|
|||||||
viewLimit= (*storage)->viewLimit;
|
viewLimit= (*storage)->viewLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert( storage );
|
||||||
|
|
||||||
// Now we assume that there's a two-layer scheme, so we operate based upon that.
|
// Now we assume that there's a two-layer scheme, so we operate based upon that.
|
||||||
|
maybeRollover();
|
||||||
|
|
||||||
Blob rv{ storage, Buffer< Mutable >{ buffer, amount } };
|
Blob rv{ storage, Buffer< Mutable >{ buffer, amount } };
|
||||||
buffer= buffer + amount;
|
buffer= buffer + amount;
|
||||||
|
@ -2,7 +2,7 @@ static_assert( __cplusplus > 2020'99 );
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Alepha.h>
|
#include <Alepha/Alepha.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -15,15 +15,14 @@ static_assert( __cplusplus > 2020'99 );
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <Alepha/Constness.h>
|
|
||||||
#include <Alepha/lifetime.h>
|
#include <Alepha/lifetime.h>
|
||||||
|
#include <Alepha/Constness.h>
|
||||||
|
#include <Alepha/Capabilities.h>
|
||||||
|
#include <Alepha/assertion.h>
|
||||||
|
#include <Alepha/Concepts.h>
|
||||||
|
|
||||||
#include <Alepha/IOStreams/String.h>
|
#include <Alepha/IOStreams/String.h>
|
||||||
|
|
||||||
#include "Concepts.h"
|
|
||||||
#include "assertion.h"
|
|
||||||
#include "Capabilities.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace Alepha::Hydrogen ::detail:: Buffer_m
|
namespace Alepha::Hydrogen ::detail:: Buffer_m
|
||||||
{
|
{
|
||||||
|
@ -1 +1,6 @@
|
|||||||
add_subdirectory( Blob.test )
|
add_subdirectory( Blob.test )
|
||||||
|
add_subdirectory( ThreadSlab.test )
|
||||||
|
|
||||||
|
target_sources( alepha PRIVATE
|
||||||
|
ThreadSlab.cc
|
||||||
|
)
|
||||||
|
@ -52,7 +52,7 @@ namespace Alepha::inline Cavorite ::detail:: DataChain_m
|
|||||||
|
|
||||||
friend DataChain;
|
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:
|
public:
|
||||||
auto
|
auto
|
||||||
@ -165,7 +165,7 @@ namespace Alepha::inline Cavorite ::detail:: DataChain_m
|
|||||||
std::copy_n( std::prev( end(), amount ), amount, rv.byte_data() );
|
std::copy_n( std::prev( end(), amount ), amount, rv.byte_data() );
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
101
Memory/ThreadSlab.h
Normal file
101
Memory/ThreadSlab.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
static_assert( __cplusplus > 2020'99 );
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Alepha/Alepha.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <Alepha/Memory/Blob.h>
|
||||||
|
|
||||||
|
namespace Alepha::Hydrogen::Memory ::detail:: ThreadSlab_m
|
||||||
|
{
|
||||||
|
inline namespace exports {}
|
||||||
|
|
||||||
|
namespace exports::inline ThreadSlab
|
||||||
|
{
|
||||||
|
template< typename T >
|
||||||
|
class Allocator;
|
||||||
|
|
||||||
|
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 shim
|
||||||
|
{
|
||||||
|
[[nodiscard]] void *allocate( std::size_t );
|
||||||
|
void deallocate( void * ) noexcept;
|
||||||
|
|
||||||
|
Blob &slab();
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
struct BlockFromSlab
|
||||||
|
{
|
||||||
|
std::byte raw[ sizeof( T ) ];
|
||||||
|
|
||||||
|
[[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:
|
||||||
|
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;
|
||||||
|
|
||||||
|
Allocator select_on_container_copy_construction() { auto rv= Allocator{}; }
|
||||||
|
|
||||||
|
Allocator()= default;
|
||||||
|
|
||||||
|
|
||||||
|
Allocator &operator= ( const Allocator &other )= default;
|
||||||
|
|
||||||
|
Allocator( const Allocator &other )= default;
|
||||||
|
|
||||||
|
Allocator( Allocator &&other ) : Allocator( std::as_const( other ) ) {}
|
||||||
|
|
||||||
|
~Allocator()= default;
|
||||||
|
|
||||||
|
static Blob &slab() { return shim::slab(); }
|
||||||
|
|
||||||
|
[[nodiscard]] T *
|
||||||
|
allocate( const std::size_t amt )
|
||||||
|
{
|
||||||
|
return reinterpret_cast< T * >( new BlockFromSlab< T >[ amt ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
deallocate( T *const p, const std::size_t /* ignored */ ) noexcept
|
||||||
|
{
|
||||||
|
return BlockFromSlab< T >::operator delete [] ( p );
|
||||||
|
}
|
||||||
|
|
||||||
|
friend constexpr bool operator == ( const Allocator &, const Allocator & ) noexcept { return true; }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Alepha::Hydrogen::Memory::inline exports::inline ThreadSlab_m
|
||||||
|
{
|
||||||
|
using namespace detail::ThreadSlab_m::exports;
|
||||||
|
}
|
49
Memory/ThreadSlab.test/0.cc
Normal file
49
Memory/ThreadSlab.test/0.cc
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
static_assert( __cplusplus > 2020'99 );
|
||||||
|
|
||||||
|
#include "../ThreadSlab.h"
|
||||||
|
|
||||||
|
#include <Alepha/Testing/test.h>
|
||||||
|
|
||||||
|
#include <Alepha/Utility/evaluation_helpers.h>
|
||||||
|
|
||||||
|
static auto init= Alepha::Utility::enroll <=[]
|
||||||
|
{
|
||||||
|
using namespace Alepha::Testing::literals;
|
||||||
|
|
||||||
|
using namespace Alepha::Memory::exports::ThreadSlab_m;
|
||||||
|
using String= ThreadSlab::String;
|
||||||
|
|
||||||
|
|
||||||
|
"Check slab usage"_test <=[]
|
||||||
|
{
|
||||||
|
std::cout << "I see " << Alepha::Memory::detail::ThreadSlab_m::shim::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::detail::ThreadSlab_m::shim::slab().reservation().use_count() << " reservations in a separate test." <<
|
||||||
|
std::endl;
|
||||||
|
};
|
||||||
|
};
|
1
Memory/ThreadSlab.test/CMakeLists.txt
Normal file
1
Memory/ThreadSlab.test/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
unit_test( 0 )
|
63
fastRandom.cc
Normal file
63
fastRandom.cc
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
static_assert( __cplusplus > 2023'00 );
|
||||||
|
|
||||||
|
#include "fastRandom.h"
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <boost/random/taus88.hpp>
|
||||||
|
|
||||||
|
namespace Alepha::Hydrogen ::detail:: fastRandom_m
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct FastRandomState
|
||||||
|
{
|
||||||
|
boost::random::taus88 fastRandomState{ std::random_device{}() };
|
||||||
|
std::uint32_t pool;
|
||||||
|
int remainingBits= 0;
|
||||||
|
|
||||||
|
void
|
||||||
|
next()
|
||||||
|
{
|
||||||
|
pool= fastRandomState();
|
||||||
|
remainingBits= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
refresh()
|
||||||
|
{
|
||||||
|
if( remainingBits == 0 ) next();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t
|
||||||
|
getBit()
|
||||||
|
{
|
||||||
|
refresh();
|
||||||
|
const std::uint32_t rv= pool & 1;
|
||||||
|
--remainingBits;
|
||||||
|
pool>>= 1;
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t
|
||||||
|
get( int count )
|
||||||
|
{
|
||||||
|
std::uint32_t rv= 0;
|
||||||
|
while( count-- )
|
||||||
|
{
|
||||||
|
rv<<= 1;
|
||||||
|
rv|= getBit();
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
thread_local FastRandomState fastRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint32_t
|
||||||
|
exports::fastRandomBits( const int numBits )
|
||||||
|
{
|
||||||
|
return fastRandom.get( numBits );
|
||||||
|
}
|
||||||
|
}
|
20
fastRandom.h
Normal file
20
fastRandom.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
static_assert( __cplusplus > 2023'00 );
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Alepha/Alepha.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace Alepha::Hydrogen ::detail:: fastRandom_m
|
||||||
|
{
|
||||||
|
inline namespace exports
|
||||||
|
{
|
||||||
|
std::uint32_t fastRandomBits( int numBits );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Alepha::Hydrogen::inline exports::inline fastRandom_m
|
||||||
|
{
|
||||||
|
using namespace detail::fastRandom_m::exports;
|
||||||
|
}
|
Reference in New Issue
Block a user