You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lnav/src/third-party/ArenaAlloc/arenaallocimpl.h

287 lines
8.8 KiB
C++

// -*- c++ -*-
/******************************************************************************
** arenaallocimpl.h
**
** Internal implementation types of the arena allocator
** MIT license
*****************************************************************************/
#ifndef _ARENA_ALLOC_IMPL_H
#define _ARENA_ALLOC_IMPL_H
#ifdef ARENA_ALLOC_DEBUG
#include <stdio.h>
#endif
namespace ArenaAlloc
{
template< typename T, typename A, typename M >
class Alloc;
// internal structure for tracking memory blocks
template < typename AllocImpl >
struct _memblock
{
// allocations are rounded up to a multiple of the size of this
// struct to maintain proper alignment for any pointer and double
// values stored in the allocation.
// A future goal is to support even stricter alignment for example
// to support cache alignment, special device dependent mappings,
// or GPU ops.
union _roundsize {
double d;
void* p;
};
_memblock* m_next{nullptr}; // blocks kept link listed for cleanup at end
std::size_t m_bufferSize; // size of the buffer
std::size_t m_index; // index of next allocatable byte in the block
char* m_buffer; // pointer to large block to allocate from
_memblock(std::size_t bufferSize, AllocImpl& allocImpl)
: m_bufferSize(roundSize(bufferSize)), m_index(0),
m_buffer(reinterpret_cast<char*>(allocImpl.allocate(
bufferSize))) // this works b/c of order of decl
{
}
std::size_t roundSize( std::size_t numBytes )
{
// this is subject to overflow. calling logic should not permit
// an attempt to allocate a really massive size.
// i.e. an attempt to allocate 10s of terabytes should be an error
return ( ( numBytes + sizeof( _roundsize ) - 1 ) /
sizeof( _roundsize ) ) * sizeof( _roundsize );
}
char * allocate( std::size_t numBytes )
{
std::size_t roundedSize = roundSize( numBytes );
if( roundedSize + m_index > m_bufferSize )
return 0;
char * ptrToReturn = &m_buffer[ m_index ];
m_index += roundedSize;
return ptrToReturn;
}
void dispose( AllocImpl& impl )
{
impl.deallocate( m_buffer );
}
~_memblock()
{
}
};
template< typename AllocatorImpl, typename Derived >
struct _memblockimplbase
{
AllocatorImpl m_alloc;
std::size_t m_refCount; // when refs -> 0 delete this
std::size_t m_defaultSize;
std::size_t m_numAllocate; // number of times allocate called
std::size_t m_numDeallocate; // number of time deallocate called
std::size_t m_numBytesAllocated; // A good estimate of amount of space used
_memblock<AllocatorImpl> * m_head;
_memblock<AllocatorImpl> * m_current;
// round up 2 next power of 2 if not already
// a power of 2
std::size_t roundpow2( std::size_t value )
{
// note this works because subtracting 1 is equivalent to
// inverting the lowest set bit and complementing any
// bits lower than that. only a power of 2
// will yield 0 in the following check
if( 0 == ( value & ( value - 1 ) ) )
return value; // already a power of 2
// fold t over itself. This will set all bits after the highest set bit of t to 1
// who said bit twiddling wasn't practical?
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
value |= value >> 32;
return value + 1;
}
_memblockimplbase( std::size_t defaultSize, AllocatorImpl& allocator ):
m_alloc( allocator ),
m_refCount( 1 ),
m_defaultSize( defaultSize ),
m_numAllocate( 0 ),
m_numDeallocate( 0 ),
m_numBytesAllocated( 0 ),
m_head( 0 ),
m_current( 0 )
{
if( m_defaultSize < 256 )
{
m_defaultSize = 256; // anything less is academic. a more practical size is 4k or more
}
else if ( m_defaultSize > 1024UL*1024*1024*16 )
{
// when this becomes a problem, this package has succeeded beyond my wildest expectations
m_defaultSize = 1024UL*1024*1024*16;
}
// for convenience block size should be a power of 2
// round up to next power of 2
m_defaultSize = roundpow2( m_defaultSize );
allocateNewBlock( m_defaultSize );
}
char * allocate( std::size_t numBytes )
{
char * ptrToReturn = m_current->allocate( numBytes );
if( !ptrToReturn )
{
allocateNewBlock( numBytes > m_defaultSize / 2 ? roundpow2( numBytes*2 ) :
m_defaultSize );
ptrToReturn = m_current->allocate( numBytes );
}
#ifdef ARENA_ALLOC_DEBUG
fprintf( stdout, "_memblockimpl=%p allocated %ld bytes at address=%p\n", this, numBytes, ptrToReturn );
#endif
++ m_numAllocate;
m_numBytesAllocated += numBytes; // does not account for the small overhead in tracking the allocation
return ptrToReturn;
}
void allocateNewBlock( std::size_t blockSize )
{
_memblock<AllocatorImpl> * newBlock = new ( m_alloc.allocate( sizeof( _memblock<AllocatorImpl> ) ) )
_memblock<AllocatorImpl>( blockSize, m_alloc );
#ifdef ARENA_ALLOC_DEBUG
fprintf( stdout, "_memblockimplbase=%p allocating a new block of size=%ld\n", this, blockSize );
#endif
if( m_head == 0 )
{
m_head = m_current = newBlock;
}
else
{
m_current->m_next = newBlock;
m_current = newBlock;
}
}
void deallocate( void * ptr )
{
++ m_numDeallocate;
}
size_t getNumAllocations() { return m_numAllocate; }
size_t getNumDeallocations() { return m_numDeallocate; }
size_t getNumBytesAllocated() { return m_numBytesAllocated; }
void clear()
{
_memblock<AllocatorImpl> * block = m_head;
while( block )
{
_memblock<AllocatorImpl> * curr = block;
block = block->m_next;
curr->dispose( m_alloc );
curr->~_memblock<AllocatorImpl>();
m_alloc.deallocate( curr );
}
}
// The ref counting model does not permit the sharing of
// this object across multiple threads unless an external locking mechanism is applied
// to ensure the atomicity of the reference count.
void incrementRefCount()
{
++m_refCount;
#ifdef ARENA_ALLOC_DEBUG
fprintf( stdout, "ref count on _memblockimplbase=%p incremented to %ld\n", this, m_refCount );
#endif
}
void decrementRefCount()
{
--m_refCount;
#ifdef ARENA_ALLOC_DEBUG
fprintf( stdout, "ref count on _memblockimplbase=%p decremented to %ld\n", this, m_refCount );
#endif
if( m_refCount == 0 )
{
Derived::destroy( static_cast<Derived*>(this) );
}
}
};
// Each allocator points to an instance of _memblockimpl which
// contains the list of _memblock objects and other tracking info
// including a refcount.
// This object is instantiated in space obtained from the allocator
// implementation. The allocator implementation is the component
// on which allocate/deallocate are called to obtain storage from.
template< typename AllocatorImpl >
struct _memblockimpl : public _memblockimplbase<AllocatorImpl, _memblockimpl<AllocatorImpl> >
{
private:
typedef struct _memblockimplbase< AllocatorImpl, _memblockimpl<AllocatorImpl> > base_t;
friend struct _memblockimplbase< AllocatorImpl, _memblockimpl<AllocatorImpl> >;
// to get around some sticky access issues between Alloc<T1> and Alloc<T2> when sharing
// the implementation.
template <typename U, typename A, typename M >
friend class Alloc;
template< typename T >
static void assign( const Alloc<T,AllocatorImpl, _memblockimpl<AllocatorImpl> >& src,
_memblockimpl *& dest );
static _memblockimpl<AllocatorImpl> * create( size_t defaultSize, AllocatorImpl& alloc )
{
return new ( alloc.allocate( sizeof( _memblockimpl ) ) ) _memblockimpl<AllocatorImpl>( defaultSize,
alloc );
}
static void destroy( _memblockimpl<AllocatorImpl> * objToDestroy )
{
AllocatorImpl allocImpl = objToDestroy->m_alloc;
objToDestroy-> ~_memblockimpl<AllocatorImpl>();
allocImpl.deallocate( objToDestroy );
}
_memblockimpl( std::size_t defaultSize, AllocatorImpl& allocImpl ):
_memblockimplbase<AllocatorImpl, _memblockimpl<AllocatorImpl> >( defaultSize, allocImpl )
{
#ifdef ARENA_ALLOC_DEBUG
fprintf( stdout, "_memblockimpl=%p constructed with default size=%ld\n", this,
base_t::m_defaultSize );
#endif
}
~_memblockimpl( )
{
#ifdef ARENA_ALLOC_DEBUG
fprintf( stdout, "~memblockimpl() called on _memblockimpl=%p\n", this );
#endif
base_t::clear();
}
};
}
#endif