1
0
forked from Alepha/Alepha
Files
Alepha/Memory/DataChain.h
ADAM David Alan Martin 6c165b1603 Blob based per-thread slab allocator
This permits "stateless" allocators which grab memory from a
`thread_local Alepha::Blob` instance.  Each allocation
sticks a malloc cookie of type
`std::shared_ptr< Alepha::Blob::StorageReservation >`
just before the base of the allocation.

The allocator object knows that it needs to `reinterpret_cast`
the malloc cookie into a shared pointer and run its destructor.
This causes the Blob's underlying reference counted allocation
to be tied to the lifetime of the allocated memory.  The intent
is to permit cheap allocation in one thread and deallocation
in another.  Each deallocation should be a single atomic
dereference operation.  Each allocation should be (usually) a
bit of pointer arithmetic and a single atomic increment operation.

This, hopefully, eliminates significant thread contention for
the global allocation mechanism between various threads in
an intensive multithreaded situation where each processing
thread thread may independently retire data objects allocated by
a single source.
2024-09-05 18:35:07 -04:00

172 lines
4.8 KiB
C++

static_assert( __cplusplus > 2020'99 );
#pragma once
#include <deque>
#include <utility>
#include <algorithm>
#include <iterator>
#include <numeric>
#include "comparisons.h"
#include "Buffer.h"
#include "Blob.h"
namespace Alepha::inline Cavorite ::detail:: DataChain_m
{
inline namespace exports
{
class DataChain;
}
using std::begin, std::end;
class exports::DataChain
{
private:
using Chain= std::deque< Blob >;
Chain chain;
template< Constness constness >
class Iterator : comparable
{
public:
using iterator_category= std::forward_iterator_tag;
using value_type= std::byte;
using difference_type= std::ptrdiff_t;
using pointer= std::byte *;
using reference= std::byte &;
private:
using ChainIter= decltype( std::declval< maybe_const_t< Chain, constness > >().begin() );
ChainIter position;
std::size_t offset;
void
advance() noexcept
{
if( ++offset < pos->size() ) return;
++pos;
offset= 0;
}
friend DataChain;
explicit Iterator( const ChainIter pos, const std::size_t offset ) noexcept : pos( pos ), offset( offset ) {}
public:
auto
value_lens() const noexcept( noexcept( std::tie( pos, offset ) ) )
{
return std::tie( pos, offset );
}
Iterator &operator ++() noexcept { advance(); return *this; }
Iterator
operator++ ( int )
noexcept
(
noexcept( Iterator{ std::declval< Iterator >() } )
and
noexcept( advance() )
)
{
Iterator rv{ *this; }
advance();
return rv;
}
maybe_const_t< std::byte &, constness >
operator *() const noexcept( noexcept( pos->byte_data()[ offset ] ) )
{
return pos->byte_data()[ offset ];
}
public:
template< typename T > void operator []( T ) const= delete;
template< typename T > void operator []( T )= delete;
using iterator= Iterator< Mutable >;
using const_iterator= Iterator< Const >;
auto begin() noexcept { using std::begin; return iterator{ begin( chain ), 0 }; }
auto end() noexcept { using std::end; return iterator{ end( chain ), 0 }; }
auto begin() const noexcept { using std::begin; return const_iterator{ begin( chain ), 0 }; }
auto end() const noexcept { using std::end; return const_iterator{ end( chain ), 0 }; }
auto cbegin() const noexcept { return begin(); }
auto cend() const noexcept { return end(); }
// Please note that this non-const view form provides direct access to the chain.
// This class doesn't store any additional state, so modification of this chain is
// likely safe, for now. But in the future, this could change. Manual modification
// of this chain is strongly discouraged.
Chain &chain_view() noexcept { return chain; }
const Chain &chain_view() const noexcept { return chain; }
std::size_t
size() const
{
using std::begin, std::end;
return std::accumulate( begin( chain_view() ), end( chain_view() ), std::size_t{},
[] ( const std::size_t lhs, const auto &rhs ) { return lhs + rhs.size(); } );
}
std::size_t chain_length() const noexcept { return chain.size(); }
std::size_t chain_empty() const noexcept { return chain.empty(); }
void clear() noexcept { chain.clear(); }
void
append( Blob &block )
{
// Base case is fast:
if( chain.empty() ) return chain.push_back( std::move( block ) );
// If we're getting a `Blob` which is contiguous we try to re-stitch them:
if( const auto contiguous= chain.back().isContiguousWith( std::move( block ) ) ) contiguous.compose();
// As a fallback, we just have to put it at the back of our list:
else chain.push_back( std::move( block ) );
}
void append( const Buffer< Const > &buffer ) { if( buffer.size() ) chain.emplace_back( buffer ); }
Blob
peekHead( const std::size_t amount )
{
if( amount == 0 ) return Blob{};
if( chain.empty() or size() < amount )
{
// TODO: Build a more specific exception for this case?
throw DataCarveToLargeError( nullptr, amount, size() );
}
// TODO: This should be in a common helper with part of `carveHead`'s internals:
Blob rv{ amount };
std::copy_n( begin(), amount, rv.byte_data() );
return rv;
}
Blob
peekTail( const std::size_t amount )
{
if( amount == 0 ) return Data{};
if( chain.empty() or size() < amount )
{
// TODO: Build a more specific exception for this case?
throw DataCarveToLargeError( nullptr, amount, size() );
}
// TODO: This should be in a common helper with part of `carveTail`'s internals:
Blob rv{ amount };
std::copy_n( std::prev( end(), amount ), amount, rv.byte_data() );
return rv;
}
};
};
}