replace old implementation with the ingeious from Rich Sposato

git-svn-id: svn://svn.code.sf.net/p/loki-lib/code/trunk@200 7ec92016-0320-0410-acc4-a06ded1c099a
This commit is contained in:
syntheticpp 2005-07-31 13:51:31 +00:00
parent 8d3a79495c
commit 7382c3dde0
2 changed files with 590 additions and 320 deletions

View file

@ -13,7 +13,8 @@
// without express or implied warranty. // without express or implied warranty.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Last update: June 20, 2001 // $Header$
#ifndef SMALLOBJ_INC_ #ifndef SMALLOBJ_INC_
#define SMALLOBJ_INC_ #define SMALLOBJ_INC_
@ -21,161 +22,249 @@
#include "Threads.h" #include "Threads.h"
#include "Singleton.h" #include "Singleton.h"
#include <cstddef> #include <cstddef>
#include <vector> #include <new> // needed for std::nothrow_t parameter.
#ifndef DEFAULT_CHUNK_SIZE #ifndef DEFAULT_CHUNK_SIZE
#define DEFAULT_CHUNK_SIZE 4096 #define DEFAULT_CHUNK_SIZE 4096
#endif #endif
#ifndef MAX_SMALL_OBJECT_SIZE #ifndef MAX_SMALL_OBJECT_SIZE
#define MAX_SMALL_OBJECT_SIZE 64 #define MAX_SMALL_OBJECT_SIZE 256
#endif #endif
#ifndef LOKI_DEFAULT_OBJECT_ALIGNMENT
#define LOKI_DEFAULT_OBJECT_ALIGNMENT 4
#endif
namespace Loki namespace Loki
{ {
//////////////////////////////////////////////////////////////////////////////// class FixedAllocator;
// class FixedAllocator
// Offers services for allocating fixed-sized objects
////////////////////////////////////////////////////////////////////////////////
class FixedAllocator
{
private:
struct Chunk
{
void Init(std::size_t blockSize, unsigned char blocks);
void* Allocate(std::size_t blockSize);
void Deallocate(void* p, std::size_t blockSize);
void Reset(std::size_t blockSize, unsigned char blocks);
void Release();
unsigned char* pData_;
unsigned char
firstAvailableBlock_,
blocksAvailable_;
};
// Internal functions
void DoDeallocate(void* p);
Chunk* VicinityFind(void* p);
// Data
std::size_t blockSize_;
unsigned char numBlocks_;
typedef std::vector<Chunk> Chunks;
Chunks chunks_;
Chunk* allocChunk_;
Chunk* deallocChunk_;
// For ensuring proper copy semantics
mutable const FixedAllocator* prev_;
mutable const FixedAllocator* next_;
public:
// Create a FixedAllocator able to manage blocks of 'blockSize' size
explicit FixedAllocator(std::size_t blockSize = 0);
FixedAllocator(const FixedAllocator&);
FixedAllocator& operator=(const FixedAllocator&);
~FixedAllocator();
void Swap(FixedAllocator& rhs);
// Allocate a memory block
void* Allocate();
// Deallocate a memory block previously allocated with Allocate()
// (if that's not the case, the behavior is undefined)
void Deallocate(void* p);
// Returns the block size with which the FixedAllocator was initialized
std::size_t BlockSize() const
{ return blockSize_; }
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// class SmallObjAllocator // class SmallObjAllocator
// Offers services for allocating small-sized objects // Manages pool of fixed-size allocators.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
class SmallObjAllocator class SmallObjAllocator
{ {
public: protected:
SmallObjAllocator( SmallObjAllocator( std::size_t pageSize, std::size_t maxObjectSize,
std::size_t chunkSize, std::size_t objectAlignSize );
std::size_t maxObjectSize);
void* Allocate(std::size_t numBytes); ~SmallObjAllocator( void );
void Deallocate(void* p, std::size_t size);
public:
void * Allocate( std::size_t size, bool doThrow );
void Deallocate( void * p, std::size_t size );
inline std::size_t GetMaxObjectSize() const { return maxSmallObjectSize_; }
inline std::size_t GetAlignment() const { return objectAlignSize_; }
private: private:
SmallObjAllocator(const SmallObjAllocator&); /// Copy-constructor is not implemented.
SmallObjAllocator& operator=(const SmallObjAllocator&); SmallObjAllocator( const SmallObjAllocator & );
/// Copy-assignment operator is not implemented.
SmallObjAllocator & operator = ( const SmallObjAllocator & );
typedef std::vector<FixedAllocator> Pool; Loki::FixedAllocator * pool_;
Pool pool_;
FixedAllocator* pLastAlloc_; std::size_t maxSmallObjectSize_;
FixedAllocator* pLastDealloc_;
std::size_t chunkSize_; std::size_t objectAlignSize_;
std::size_t maxObjectSize_;
}; };
////////////////////////////////////////////////////////////////////////////////
// class AllocatorSingleton
// This template class is derived from SmallObjAllocator in order to pass template
// arguments into SmallObjAllocator, and still have a default constructor for the
// singleton.
////////////////////////////////////////////////////////////////////////////////
template
<
template <class> class ThreadingModel,
std::size_t chunkSize,
std::size_t maxSmallObjectSize,
std::size_t objectAlignSize
>
class AllocatorSingleton : public SmallObjAllocator
{
public:
inline AllocatorSingleton() :
SmallObjAllocator( chunkSize, maxSmallObjectSize, objectAlignSize )
{}
inline ~AllocatorSingleton( void ) {}
private:
/// Copy-constructor is not implemented.
AllocatorSingleton( const AllocatorSingleton & );
/// Copy-assignment operator is not implemented.
AllocatorSingleton & operator = ( const AllocatorSingleton & );
};
////////////////////////////////////////////////////////////////////////////////
// class SmallObjectBase
// Base class for small object allocation classes.
////////////////////////////////////////////////////////////////////////////////
template
<
template <class> class ThreadingModel,
std::size_t chunkSize,
std::size_t maxSmallObjectSize,
std::size_t objectAlignSize,
template <class> class LifetimePolicy
>
class SmallObjectBase
{
#if (MAX_SMALL_OBJECT_SIZE != 0) && (DEFAULT_CHUNK_SIZE != 0) && (LOKI_DEFAULT_OBJECT_ALIGNMENT != 0)
/// Defines type of allocator.
typedef AllocatorSingleton< ThreadingModel, chunkSize,
maxSmallObjectSize, objectAlignSize > MyAllocator;
/// Defines type for thread-safety locking mechanism.
typedef ThreadingModel< MyAllocator > MyThreadingModel;
/// Defines singleton made from allocator.
typedef Loki::SingletonHolder< MyAllocator, Loki::CreateStatic,
LifetimePolicy, ThreadingModel > MyAllocatorSingleton;
public:
/** Throwing single-object new.
@note The exception specification is commented to prevent warning
from MS compiler.
*/
static void * operator new ( std::size_t size ) // throw ( std::bad_alloc )
{
typename MyThreadingModel::Lock lock;
(void)lock; // get rid of warning
return MyAllocatorSingleton::Instance().Allocate( size, true );
}
/// Non-throwing single-object new.
static void * operator new ( std::size_t size, const std::nothrow_t & ) throw ()
{
typename MyThreadingModel::Lock lock;
(void)lock; // get rid of warning
return MyAllocatorSingleton::Instance().Allocate( size, false );
}
/// Placement single-object new.
inline static void * operator new ( std::size_t size, void * place )
{
return ::operator new( size, place );
}
/// Single-object delete.
static void operator delete ( void * p, std::size_t size ) throw ()
{
typename MyThreadingModel::Lock lock;
(void)lock; // get rid of warning
MyAllocatorSingleton::Instance().Deallocate( p, size );
}
/// Non-throwing single-object delete.
static void operator delete ( void * p, std::size_t size,
const std::nothrow_t & ) throw()
{
typename MyThreadingModel::Lock lock;
(void)lock; // get rid of warning
MyAllocatorSingleton::Instance().Deallocate( p, size );
}
/// Placement single-object delete.
inline static void operator delete ( void * p, void * place )
{
::operator delete ( p, place );
}
#endif // #if default template parameters are not zero
protected:
inline SmallObjectBase( void ) {}
inline SmallObjectBase( const SmallObjectBase & ) {}
inline SmallObjectBase & operator = ( const SmallObjectBase & ) {}
inline ~SmallObjectBase() {}
}; // end class SmallObjectBase
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// class SmallObject // class SmallObject
// Base class for polymorphic small objects, offers fast // Base class for polymorphic small objects, offers fast allocations &
// allocations/deallocations // deallocations. Destructor is virtual and public.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
template template
< <
template <class> class ThreadingModel = DEFAULT_THREADING, template <class> class ThreadingModel = DEFAULT_THREADING,
std::size_t chunkSize = DEFAULT_CHUNK_SIZE, std::size_t chunkSize = DEFAULT_CHUNK_SIZE,
std::size_t maxSmallObjectSize = MAX_SMALL_OBJECT_SIZE std::size_t maxSmallObjectSize = MAX_SMALL_OBJECT_SIZE,
std::size_t objectAlignSize = LOKI_DEFAULT_OBJECT_ALIGNMENT,
template <class> class LifetimePolicy = Loki::NoDestroy
> >
class SmallObject : public ThreadingModel< class SmallObject : public SmallObjectBase< ThreadingModel, chunkSize,
SmallObject<ThreadingModel, chunkSize, maxSmallObjectSize> > maxSmallObjectSize, objectAlignSize, LifetimePolicy >
{ {
typedef ThreadingModel< SmallObject<ThreadingModel,
chunkSize, maxSmallObjectSize> > MyThreadingModel;
struct MySmallObjAllocator : public SmallObjAllocator
{
MySmallObjAllocator()
: SmallObjAllocator(chunkSize, maxSmallObjectSize)
{}
};
// The typedef below would make things much simpler,
// but MWCW won't like it
// typedef SingletonHolder<MySmallObjAllocator/*, CreateStatic,
// DefaultLifetime, ThreadingModel*/> MyAllocator;
public: public:
static void* operator new(std::size_t size)
{
#if (MAX_SMALL_OBJECT_SIZE != 0) && (DEFAULT_CHUNK_SIZE != 0)
typename MyThreadingModel::Lock lock;
(void)lock; // get rid of warning
return SingletonHolder<MySmallObjAllocator, CreateStatic,
PhoenixSingleton>::Instance().Allocate(size);
#else
return ::operator new(size);
#endif
}
static void operator delete(void* p, std::size_t size)
{
#if (MAX_SMALL_OBJECT_SIZE != 0) && (DEFAULT_CHUNK_SIZE != 0)
typename MyThreadingModel::Lock lock;
(void)lock; // get rid of warning
SingletonHolder<MySmallObjAllocator, CreateStatic,
PhoenixSingleton>::Instance().Deallocate(p, size);
#else
::operator delete(p);
#endif
}
virtual ~SmallObject() {} virtual ~SmallObject() {}
}; protected:
inline SmallObject( void ) {}
private:
/// Copy-constructor is not implemented.
SmallObject( const SmallObject & );
/// Copy-assignment operator is not implemented.
SmallObject & operator = ( const SmallObject & );
}; // end class SmallObject
////////////////////////////////////////////////////////////////////////////////
// class SmallValueObject
// Base class for small objects with value semantics - offers fast allocations &
// deallocations. Destructor is non-virtual, inline, and protected.
////////////////////////////////////////////////////////////////////////////////
template
<
template <class> class ThreadingModel = DEFAULT_THREADING,
std::size_t chunkSize = DEFAULT_CHUNK_SIZE,
std::size_t maxSmallObjectSize = MAX_SMALL_OBJECT_SIZE,
std::size_t objectAlignSize = LOKI_DEFAULT_OBJECT_ALIGNMENT,
template <class> class LifetimePolicy = Loki::NoDestroy
>
class SmallValueObject : public SmallObjectBase< ThreadingModel, chunkSize,
maxSmallObjectSize, objectAlignSize, LifetimePolicy >
{
protected:
inline SmallValueObject( void ) {}
inline SmallValueObject( const SmallValueObject & ) {}
inline SmallValueObject & operator = ( const SmallValueObject & ) {}
inline ~SmallValueObject() {}
}; // end class SmallValueObject
} // namespace Loki } // namespace Loki
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Change log: // Change log:
// June 20, 2001: ported by Nick Thurn to gcc 2.95.3. Kudos, Nick!!! // June 20, 2001: ported by Nick Thurn to gcc 2.95.3. Kudos, Nick!!!
// Nov. 26, 2004: re-implemented by Rich Sposato.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
#endif // SMALLOBJ_INC_ #endif // SMALLOBJ_INC_
// $Log$
// Revision 1.2 2005/07/31 13:51:31 syntheticpp
// replace old implementation with the ingeious from Rich Sposato
//
// Revision 1.2 2005/07/22 00:22:38 rich_sposato
// Added SmallValueObject, SmallObjectBase, and AllocatorSingleton classes.
//

View file

@ -13,29 +13,122 @@
// without express or implied warranty. // without express or implied warranty.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Last update: March 20, 2001 // $Header$
#include "SmallObj.h" #include "SmallObj.h"
#include <cassert> #include <cassert>
#include <vector>
#include <algorithm> #include <algorithm>
#include <functional> #include <functional>
using namespace Loki; using namespace Loki;
namespace Loki
{
////////////////////////////////////////////////////////////////////////////////
// class FixedAllocator
// Offers services for allocating fixed-sized objects
////////////////////////////////////////////////////////////////////////////////
class FixedAllocator
{
private:
struct Chunk
{
bool Init( std::size_t blockSize, unsigned char blocks );
void* Allocate(std::size_t blockSize);
void Deallocate(void* p, std::size_t blockSize);
void Reset(std::size_t blockSize, unsigned char blocks);
void Release();
inline bool HasBlock( unsigned char * p, std::size_t chunkLength ) const
{ return ( pData_ <= p ) && ( p < pData_ + chunkLength ); }
inline bool HasAvailable( unsigned char numBlocks ) const
{ return ( blocksAvailable_ == numBlocks ); }
inline bool IsFilled( void ) const
{ return ( 0 == blocksAvailable_ ); }
unsigned char* pData_;
unsigned char
firstAvailableBlock_,
blocksAvailable_;
};
// Internal functions
void DoDeallocate(void* p);
bool MakeNewChunk( void );
Chunk * VicinityFind( void * p );
/// Not implemented.
FixedAllocator(const FixedAllocator&);
/// Not implemented.
FixedAllocator& operator=(const FixedAllocator&);
// Data
std::size_t blockSize_;
unsigned char numBlocks_;
typedef std::vector<Chunk> Chunks;
typedef Chunks::iterator ChunkIter;
typedef Chunks::const_iterator ChunkCIter;
Chunks chunks_;
Chunk* allocChunk_;
Chunk* deallocChunk_;
Chunk * emptyChunk_;
public:
// Create a FixedAllocator able to manage blocks of 'blockSize' size
FixedAllocator();
~FixedAllocator();
void Initialize( std::size_t blockSize, std::size_t pageSize );
// Allocate a memory block
void * Allocate( void );
// Deallocate a memory block previously allocated with Allocate()
// (if that's not the case, the behavior is undefined)
bool Deallocate( void * p, bool doChecks );
// Returns the block size with which the FixedAllocator was initialized
inline std::size_t BlockSize() const
{ return blockSize_; }
};
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// FixedAllocator::Chunk::Init // FixedAllocator::Chunk::Init
// Initializes a chunk object // Initializes a chunk object
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void FixedAllocator::Chunk::Init(std::size_t blockSize, unsigned char blocks) bool FixedAllocator::Chunk::Init( std::size_t blockSize, unsigned char blocks )
{ {
assert(blockSize > 0); assert(blockSize > 0);
assert(blocks > 0); assert(blocks > 0);
// Overflow check // Overflow check
assert((blockSize * blocks) / blockSize == blocks); const std::size_t allocSize = blockSize * blocks;
assert( allocSize / blockSize == blocks);
pData_ = new unsigned char[blockSize * blocks]; #ifdef USE_NEW_TO_ALLOCATE
Reset(blockSize, blocks); // If this new operator fails, it will throw, and the exception will get
// caught one layer up.
pData_ = new unsigned char[ allocSize ];
#else
// malloc can't throw, so its only way to indicate an error is to return
// a NULL pointer, so we have to check for that.
pData_ = static_cast< unsigned char * >( ::malloc( allocSize ) );
if ( NULL == pData_ ) return false;
#endif
Reset( blockSize, blocks );
return true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -68,7 +161,12 @@ void FixedAllocator::Chunk::Reset(std::size_t blockSize, unsigned char blocks)
void FixedAllocator::Chunk::Release() void FixedAllocator::Chunk::Release()
{ {
delete[] pData_; assert( NULL != pData_ );
#ifdef USE_NEW_TO_ALLOCATE
delete [] pData_;
#else
::free( static_cast< void * >( pData_ ) );
#endif
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -78,13 +176,11 @@ void FixedAllocator::Chunk::Release()
void* FixedAllocator::Chunk::Allocate(std::size_t blockSize) void* FixedAllocator::Chunk::Allocate(std::size_t blockSize)
{ {
if (!blocksAvailable_) return 0; if ( IsFilled() ) return NULL;
assert((firstAvailableBlock_ * blockSize) / blockSize == assert((firstAvailableBlock_ * blockSize) / blockSize ==
firstAvailableBlock_); firstAvailableBlock_);
unsigned char * pResult = pData_ + (firstAvailableBlock_ * blockSize);
unsigned char* pResult =
pData_ + (firstAvailableBlock_ * blockSize);
firstAvailableBlock_ = *pResult; firstAvailableBlock_ = *pResult;
--blocksAvailable_; --blocksAvailable_;
@ -118,52 +214,12 @@ void FixedAllocator::Chunk::Deallocate(void* p, std::size_t blockSize)
// Creates a FixedAllocator object of a fixed block size // Creates a FixedAllocator object of a fixed block size
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
FixedAllocator::FixedAllocator(std::size_t blockSize) FixedAllocator::FixedAllocator()
: blockSize_(blockSize) : blockSize_( 0 )
, allocChunk_(0) , allocChunk_( NULL )
, deallocChunk_(0) , deallocChunk_( NULL )
, emptyChunk_( NULL )
{ {
assert(blockSize_ > 0);
prev_ = next_ = this;
std::size_t numBlocks = DEFAULT_CHUNK_SIZE / blockSize;
if (numBlocks > UCHAR_MAX) numBlocks = UCHAR_MAX;
else if (numBlocks == 0) numBlocks = 8 * blockSize;
numBlocks_ = static_cast<unsigned char>(numBlocks);
assert(numBlocks_ == numBlocks);
}
////////////////////////////////////////////////////////////////////////////////
// FixedAllocator::FixedAllocator(const FixedAllocator&)
// Creates a FixedAllocator object of a fixed block size
////////////////////////////////////////////////////////////////////////////////
FixedAllocator::FixedAllocator(const FixedAllocator& rhs)
: blockSize_(rhs.blockSize_)
, numBlocks_(rhs.numBlocks_)
, chunks_(rhs.chunks_)
{
prev_ = &rhs;
next_ = rhs.next_;
rhs.next_->prev_ = this;
rhs.next_ = this;
allocChunk_ = rhs.allocChunk_
? &chunks_.front() + (rhs.allocChunk_ - &rhs.chunks_.front())
: 0;
deallocChunk_ = rhs.deallocChunk_
? &chunks_.front() + (rhs.deallocChunk_ - &rhs.chunks_.front())
: 0;
}
FixedAllocator& FixedAllocator::operator=(const FixedAllocator& rhs)
{
FixedAllocator copy(rhs);
copy.Swap(*this);
return *this;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -172,35 +228,57 @@ FixedAllocator& FixedAllocator::operator=(const FixedAllocator& rhs)
FixedAllocator::~FixedAllocator() FixedAllocator::~FixedAllocator()
{ {
if (prev_ != this) for ( ChunkIter i( chunks_.begin() ); i != chunks_.end(); ++i )
{
prev_->next_ = next_;
next_->prev_ = prev_;
return;
}
assert(prev_ == next_);
Chunks::iterator i = chunks_.begin();
for (; i != chunks_.end(); ++i)
{
assert(i->blocksAvailable_ == numBlocks_);
i->Release(); i->Release();
}
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// FixedAllocator::Swap // FixedAllocator::Initialize
// Initializes the operational constraints for the FixedAllocator
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void FixedAllocator::Swap(FixedAllocator& rhs) void FixedAllocator::Initialize( std::size_t blockSize, std::size_t pageSize )
{ {
using namespace std; assert( blockSize > 0 );
assert( pageSize >= blockSize );
blockSize_ = blockSize;
swap(blockSize_, rhs.blockSize_); std::size_t numBlocks = pageSize / blockSize;
swap(numBlocks_, rhs.numBlocks_); if (numBlocks > UCHAR_MAX) numBlocks = UCHAR_MAX;
chunks_.swap(rhs.chunks_); else if ( numBlocks < 8 ) numBlocks = 8;
swap(allocChunk_, rhs.allocChunk_);
swap(deallocChunk_, rhs.deallocChunk_); numBlocks_ = static_cast<unsigned char>(numBlocks);
assert(numBlocks_ == numBlocks);
}
////////////////////////////////////////////////////////////////////////////////
// FixedAllocator::MakeNewChunk
// Allocates a new Chunk for a FixedAllocator.
////////////////////////////////////////////////////////////////////////////////
bool FixedAllocator::MakeNewChunk( void )
{
bool allocated = false;
try
{
// Calling chunks_.reserve *before* creating and initializing the new
// Chunk means that nothing is leaked by this function in case an
// exception is thrown from reserve.
chunks_.reserve( chunks_.size() + 1 );
Chunk newChunk;
allocated = newChunk.Init( blockSize_, numBlocks_ );
if ( allocated )
chunks_.push_back( newChunk );
}
catch ( ... )
{
allocated = false;
}
if ( !allocated ) return false;
allocChunk_ = &chunks_.back();
deallocChunk_ = &chunks_.front();
return true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -208,35 +286,50 @@ void FixedAllocator::Swap(FixedAllocator& rhs)
// Allocates a block of fixed size // Allocates a block of fixed size
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void* FixedAllocator::Allocate() void * FixedAllocator::Allocate( void )
{ {
if (allocChunk_ == 0 || allocChunk_->blocksAvailable_ == 0) // prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
if ( ( NULL == allocChunk_ ) || allocChunk_->IsFilled() )
{ {
Chunks::iterator i = chunks_.begin(); if ( NULL != emptyChunk_ )
for (;; ++i)
{ {
if (i == chunks_.end()) allocChunk_ = emptyChunk_;
emptyChunk_ = NULL;
}
else
{ {
// Initialize for ( ChunkIter i( chunks_.begin() ); ; ++i )
chunks_.reserve(chunks_.size() + 1); {
Chunk newChunk; if ( chunks_.end() == i )
newChunk.Init(blockSize_, numBlocks_); {
chunks_.push_back(newChunk); if ( !MakeNewChunk() )
allocChunk_ = &chunks_.back(); return NULL;
deallocChunk_ = &chunks_.front();
break; break;
} }
if (i->blocksAvailable_ > 0) if ( !i->IsFilled() )
{ {
allocChunk_ = &*i; allocChunk_ = &*i;
break; break;
} }
} }
} }
assert(allocChunk_ != 0); }
assert(allocChunk_->blocksAvailable_ > 0); else if ( allocChunk_ == emptyChunk_)
// detach emptyChunk_ from allocChunk_, because after
// calling allocChunk_->Allocate(blockSize_); the chunk
// isn't any more empty
emptyChunk_ = NULL;
return allocChunk_->Allocate(blockSize_); assert( allocChunk_ != NULL );
assert( !allocChunk_->IsFilled() );
void *place = allocChunk_->Allocate(blockSize_);
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
return place;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -245,16 +338,28 @@ void* FixedAllocator::Allocate()
// (undefined behavior if called with the wrong pointer) // (undefined behavior if called with the wrong pointer)
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void FixedAllocator::Deallocate(void* p) bool FixedAllocator::Deallocate( void * p, bool doChecks )
{ {
if ( doChecks )
{
assert(!chunks_.empty()); assert(!chunks_.empty());
assert(&chunks_.front() <= deallocChunk_); assert(&chunks_.front() <= deallocChunk_);
assert(&chunks_.back() >= deallocChunk_); assert(&chunks_.back() >= deallocChunk_);
assert( &chunks_.front() <= allocChunk_ );
assert( &chunks_.back() >= allocChunk_ );
}
deallocChunk_ = VicinityFind(p); Chunk * foundChunk = VicinityFind( p );
assert(deallocChunk_); if ( doChecks )
{
assert( NULL != foundChunk );
}
else if ( NULL == foundChunk )
return false;
deallocChunk_ = foundChunk;
DoDeallocate(p); DoDeallocate(p);
return true;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -262,11 +367,12 @@ void FixedAllocator::Deallocate(void* p)
// Finds the chunk corresponding to a pointer, using an efficient search // Finds the chunk corresponding to a pointer, using an efficient search
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
FixedAllocator::Chunk* FixedAllocator::VicinityFind(void* p) FixedAllocator::Chunk * FixedAllocator::VicinityFind( void * p )
{ {
assert(!chunks_.empty()); if ( chunks_.empty() ) return NULL;
assert(deallocChunk_); assert(deallocChunk_);
unsigned char * pc = static_cast< unsigned char * >( p );
const std::size_t chunkLength = numBlocks_ * blockSize_; const std::size_t chunkLength = numBlocks_ * blockSize_;
Chunk* lo = deallocChunk_; Chunk* lo = deallocChunk_;
@ -275,31 +381,33 @@ FixedAllocator::Chunk* FixedAllocator::VicinityFind(void* p)
Chunk* hiBound = &chunks_.back() + 1; Chunk* hiBound = &chunks_.back() + 1;
// Special case: deallocChunk_ is the last in the array // Special case: deallocChunk_ is the last in the array
if (hi == hiBound) hi = 0; if (hi == hiBound) hi = NULL;
for (;;) for (;;)
{ {
if (lo) if (lo)
{ {
if (p >= lo->pData_ && p < lo->pData_ + chunkLength) if ( lo->HasBlock( pc, chunkLength ) ) return lo;
if ( lo == loBound )
{ {
return lo; lo = NULL;
if ( NULL == hi ) break;
} }
if (lo == loBound) lo = 0;
else --lo; else --lo;
} }
if (hi) if (hi)
{ {
if (p >= hi->pData_ && p < hi->pData_ + chunkLength) if ( hi->HasBlock( pc, chunkLength ) ) return hi;
if ( ++hi == hiBound )
{ {
return hi; hi = NULL;
} if ( NULL == lo ) break;
if (++hi == hiBound) hi = 0;
} }
} }
assert(false); }
return 0;
return NULL;
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -309,129 +417,202 @@ FixedAllocator::Chunk* FixedAllocator::VicinityFind(void* p)
void FixedAllocator::DoDeallocate(void* p) void FixedAllocator::DoDeallocate(void* p)
{ {
assert(deallocChunk_->pData_ <= p); assert( deallocChunk_->HasBlock( static_cast< unsigned char * >( p ),
assert(deallocChunk_->pData_ + numBlocks_ * blockSize_ > p); numBlocks_ * blockSize_ ) );
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
// call into the chunk, will adjust the inner list but won't release memory // call into the chunk, will adjust the inner list but won't release memory
deallocChunk_->Deallocate(p, blockSize_); deallocChunk_->Deallocate(p, blockSize_);
if (deallocChunk_->blocksAvailable_ == numBlocks_) if ( deallocChunk_->HasAvailable( numBlocks_ ) )
{ {
// deallocChunk_ is completely free, should we release it? assert( emptyChunk_ != deallocChunk_ );
// deallocChunk_ is empty, but a Chunk is only released if there are 2
Chunk& lastChunk = chunks_.back(); // empty chunks. Since emptyChunk_ may only point to a previously
// cleared Chunk, if it points to something else besides deallocChunk_,
if (&lastChunk == deallocChunk_) // then FixedAllocator currently has 2 empty Chunks.
if ( NULL != emptyChunk_ )
{ {
// check if we have two last chunks empty // If last Chunk is empty, just change what deallocChunk_
// points to, and release the last. Otherwise, swap an empty
if (chunks_.size() > 1 && // Chunk with the last, and then release it.
deallocChunk_[-1].blocksAvailable_ == numBlocks_) Chunk * lastChunk = &chunks_.back();
{ if ( lastChunk == deallocChunk_ )
// Two free chunks, discard the last one deallocChunk_ = emptyChunk_;
lastChunk.Release(); else if ( lastChunk != emptyChunk_ )
chunks_.pop_back(); std::swap( *emptyChunk_, *lastChunk );
allocChunk_ = deallocChunk_ = &chunks_.front(); assert( lastChunk->HasAvailable( numBlocks_ ) );
} lastChunk->Release();
return;
}
if (lastChunk.blocksAvailable_ == numBlocks_)
{
// Two free blocks, discard one
lastChunk.Release();
chunks_.pop_back(); chunks_.pop_back();
allocChunk_ = deallocChunk_; allocChunk_ = deallocChunk_;
} }
else emptyChunk_ = deallocChunk_;
{
// move the empty chunk to the end
std::swap(*deallocChunk_, lastChunk);
allocChunk_ = &chunks_.back();
}
} }
// prove either emptyChunk_ points nowhere, or points to a truly empty Chunk.
assert( ( NULL == emptyChunk_ ) || ( emptyChunk_->HasAvailable( numBlocks_ ) ) );
}
////////////////////////////////////////////////////////////////////////////////
// GetOffset
// Calculates index into array where a FixedAllocator of numBytes is located.
////////////////////////////////////////////////////////////////////////////////
inline std::size_t GetOffset( std::size_t numBytes, std::size_t alignment )
{
const std::size_t alignExtra = alignment-1;
return ( numBytes + alignExtra ) / alignment;
}
////////////////////////////////////////////////////////////////////////////////
// DefaultAllocator
// Call to default allocator when SmallObjAllocator decides not to handle request.
////////////////////////////////////////////////////////////////////////////////
void * DefaultAllocator( std::size_t numBytes, bool doThrow )
{
#ifdef USE_NEW_TO_ALLOCATE
return doThrow ? ::operator new( numBytes ) :
::operator new( numBytes, std::nothrow_t() );
#else
void * p = ::malloc( numBytes );
if ( doThrow && ( NULL == p ) )
throw std::bad_alloc();
return p;
#endif
}
////////////////////////////////////////////////////////////////////////////////
// DefaultDeallocator
// Call to default deallocator when SmallObjAllocator decides not to handle request.
////////////////////////////////////////////////////////////////////////////////
void DefaultDeallocator( void * p )
{
#ifdef USE_NEW_TO_ALLOCATE
::operator delete( p );
#else
::free( p );
#endif
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// SmallObjAllocator::SmallObjAllocator // SmallObjAllocator::SmallObjAllocator
// Creates an allocator for small objects given chunk size and maximum 'small' // Creates a SmallObjAllocator, and all the FixedAllocators within it. Each
// object size // FixedAllocator is then initialized to use the correct Chunk size.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
SmallObjAllocator::SmallObjAllocator( SmallObjAllocator::SmallObjAllocator( std::size_t pageSize,
std::size_t chunkSize, std::size_t maxObjectSize, std::size_t objectAlignSize ) :
std::size_t maxObjectSize) pool_( NULL ),
: pLastAlloc_(0), pLastDealloc_(0) maxSmallObjectSize_( maxObjectSize ),
, chunkSize_(chunkSize), maxObjectSize_(maxObjectSize) objectAlignSize_( objectAlignSize )
{ {
assert( 0 != objectAlignSize );
const std::size_t allocCount = GetOffset( maxObjectSize, objectAlignSize );
pool_ = new FixedAllocator[ allocCount ];
for ( std::size_t i = 0; i < allocCount; ++i )
pool_[ i ].Initialize( ( i+1 ) * objectAlignSize, pageSize );
} }
namespace { // anoymous ////////////////////////////////////////////////////////////////////////////////
// SmallObjAllocator::~SmallObjAllocator
// Deletes all memory consumed by SmallObjAllocator.
// This deletes all the FixedAllocator's in the pool.
////////////////////////////////////////////////////////////////////////////////
// See LWG DR #270 SmallObjAllocator::~SmallObjAllocator( void )
struct CompareFixedAllocatorSize
: std::binary_function<const FixedAllocator &, std::size_t, bool>
{ {
bool operator()(const FixedAllocator &x, std::size_t numBytes) const delete [] pool_;
{ }
return x.BlockSize() < numBytes;
}
};
} // anoymous namespace
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// SmallObjAllocator::Allocate // SmallObjAllocator::Allocate
// Allocates 'numBytes' memory // Handles request to allocate numBytes for 1 object.
// Uses an internal pool of FixedAllocator objects for small objects // This acts in constant-time - except for the calls to DefaultAllocator
// and sometimes FixedAllocator::Allocate. It throws bad_alloc only if the
// doThrow parameter is true and can't allocate another block. Otherwise, it
// provides the no-throw exception safety level.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void* SmallObjAllocator::Allocate(std::size_t numBytes) void * SmallObjAllocator::Allocate( std::size_t numBytes, bool doThrow )
{ {
if (numBytes > maxObjectSize_) return operator new(numBytes); if ( numBytes > GetMaxObjectSize() )
return DefaultAllocator( numBytes, doThrow );
if (pLastAlloc_ && pLastAlloc_->BlockSize() == numBytes) assert( NULL != pool_ );
if ( 0 == numBytes ) numBytes = 1;
const std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1;
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
assert( index < allocCount );
FixedAllocator & allocator = pool_[ index ];
assert( allocator.BlockSize() >= numBytes );
assert( allocator.BlockSize() < numBytes + GetAlignment() );
void * place = allocator.Allocate();
if ( ( NULL == place ) && doThrow )
{ {
return pLastAlloc_->Allocate(); #if _MSC_VER
throw std::bad_alloc( "could not allocate small object" );
#else
// GCC did not like a literal string passed to std::bad_alloc.
// so just throw the default-constructed exception.
throw std::bad_alloc();
#endif
} }
Pool::iterator i = std::lower_bound(pool_.begin(), pool_.end(), numBytes, return place;
CompareFixedAllocatorSize());
if (i == pool_.end() || i->BlockSize() != numBytes)
{
i = pool_.insert(i, FixedAllocator(numBytes));
pLastDealloc_ = &*pool_.begin();
}
pLastAlloc_ = &*i;
return pLastAlloc_->Allocate();
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// SmallObjAllocator::Deallocate // SmallObjAllocator::Deallocate
// Deallocates memory previously allocated with Allocate // Handles request to deallocate numBytes for 1 object.
// (undefined behavior if you pass any other pointer) // This will act in constant-time - except for the calls to DefaultDeallocator
// and sometimes FixedAllocator::Deallocate. It will never throw.
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void SmallObjAllocator::Deallocate(void* p, std::size_t numBytes) void SmallObjAllocator::Deallocate( void * p, std::size_t numBytes )
{ {
if (numBytes > maxObjectSize_) return operator delete(p); if ( NULL == p ) return;
if ( numBytes > GetMaxObjectSize() )
if (pLastDealloc_ && pLastDealloc_->BlockSize() == numBytes)
{ {
pLastDealloc_->Deallocate(p); DefaultDeallocator( p );
return; return;
} }
Pool::iterator i = std::lower_bound(pool_.begin(), pool_.end(), numBytes, assert( NULL != pool_ );
CompareFixedAllocatorSize()); if ( 0 == numBytes ) numBytes = 1;
assert(i != pool_.end()); const std::size_t index = GetOffset( numBytes, GetAlignment() ) - 1;
assert(i->BlockSize() == numBytes); const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
pLastDealloc_ = &*i; assert( index < allocCount );
pLastDealloc_->Deallocate(p); FixedAllocator & allocator = pool_[ index ];
assert( allocator.BlockSize() >= numBytes );
assert( allocator.BlockSize() < numBytes + GetAlignment() );
const bool found = allocator.Deallocate( p, true );
assert( found );
} }
} // end namespace Loki
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Change log: // Change log:
// March 20: fix exception safety issue in FixedAllocator::Allocate // March 20: fix exception safety issue in FixedAllocator::Allocate
// (thanks to Chris Udazvinis for pointing that out) // (thanks to Chris Udazvinis for pointing that out)
// June 20, 2001: ported by Nick Thurn to gcc 2.95.3. Kudos, Nick!!! // June 20, 2001: ported by Nick Thurn to gcc 2.95.3. Kudos, Nick!!!
// Aug 02, 2002: Fix in VicinityFind sent by Pavel Vozenilek // Aug 02, 2002: Fix in VicinityFind sent by Pavel Vozenilek
// Nov 26, 2004: Re-implemented by Rich Sposato.
// Jun 22, 2005: Fix in FixedAllocator::Allocate by Chad Lehman
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// $Log$
// Revision 1.2 2005/07/31 13:51:31 syntheticpp
// replace old implementation with the ingeious from Rich Sposato
//
// Revision 1.3 2005/07/28 07:02:58 syntheticpp
// gcc -pedantic correction
//
// Revision 1.2 2005/07/20 08:44:19 syntheticpp
// move MSVC
//
// Revision 1.9 2005/07/20 00:34:15 rich_sposato
// Fixed overflow bug in calculating number of blocks per Chunk.
//