From 7382c3dde0d4a8e2346e9a1c30d978ce5cf7d3e4 Mon Sep 17 00:00:00 2001 From: syntheticpp Date: Sun, 31 Jul 2005 13:51:31 +0000 Subject: [PATCH] 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 --- include/loki/SmallObj.h | 325 ++++++++++++++-------- src/SmallObj.cpp | 585 ++++++++++++++++++++++++++-------------- 2 files changed, 590 insertions(+), 320 deletions(-) diff --git a/include/loki/SmallObj.h b/include/loki/SmallObj.h index ba55220..44278b7 100644 --- a/include/loki/SmallObj.h +++ b/include/loki/SmallObj.h @@ -13,7 +13,8 @@ // without express or implied warranty. //////////////////////////////////////////////////////////////////////////////// -// Last update: June 20, 2001 +// $Header$ + #ifndef SMALLOBJ_INC_ #define SMALLOBJ_INC_ @@ -21,161 +22,249 @@ #include "Threads.h" #include "Singleton.h" #include -#include +#include // needed for std::nothrow_t parameter. #ifndef DEFAULT_CHUNK_SIZE #define DEFAULT_CHUNK_SIZE 4096 #endif #ifndef MAX_SMALL_OBJECT_SIZE -#define MAX_SMALL_OBJECT_SIZE 64 +#define MAX_SMALL_OBJECT_SIZE 256 #endif +#ifndef LOKI_DEFAULT_OBJECT_ALIGNMENT +#define LOKI_DEFAULT_OBJECT_ALIGNMENT 4 +#endif + + namespace Loki { -//////////////////////////////////////////////////////////////////////////////// -// class FixedAllocator -// Offers services for allocating fixed-sized objects -//////////////////////////////////////////////////////////////////////////////// + class FixedAllocator; - 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 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 -// Offers services for allocating small-sized objects +// Manages pool of fixed-size allocators. //////////////////////////////////////////////////////////////////////////////// class SmallObjAllocator { + protected: + SmallObjAllocator( std::size_t pageSize, std::size_t maxObjectSize, + std::size_t objectAlignSize ); + + ~SmallObjAllocator( void ); + public: - SmallObjAllocator( - std::size_t chunkSize, - std::size_t maxObjectSize); - - void* Allocate(std::size_t numBytes); - void Deallocate(void* p, std::size_t size); - + 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: - SmallObjAllocator(const SmallObjAllocator&); - SmallObjAllocator& operator=(const SmallObjAllocator&); - - typedef std::vector Pool; - Pool pool_; - FixedAllocator* pLastAlloc_; - FixedAllocator* pLastDealloc_; - std::size_t chunkSize_; - std::size_t maxObjectSize_; + /// Copy-constructor is not implemented. + SmallObjAllocator( const SmallObjAllocator & ); + /// Copy-assignment operator is not implemented. + SmallObjAllocator & operator = ( const SmallObjAllocator & ); + + Loki::FixedAllocator * pool_; + + std::size_t maxSmallObjectSize_; + + std::size_t objectAlignSize_; }; +//////////////////////////////////////////////////////////////////////////////// +// 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 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 ThreadingModel, + std::size_t chunkSize, + std::size_t maxSmallObjectSize, + std::size_t objectAlignSize, + template 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 -// Base class for polymorphic small objects, offers fast -// allocations/deallocations +// Base class for polymorphic small objects, offers fast allocations & +// deallocations. Destructor is virtual and public. //////////////////////////////////////////////////////////////////////////////// template < template class ThreadingModel = DEFAULT_THREADING, 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 LifetimePolicy = Loki::NoDestroy > - class SmallObject : public ThreadingModel< - SmallObject > + class SmallObject : public SmallObjectBase< ThreadingModel, chunkSize, + maxSmallObjectSize, objectAlignSize, LifetimePolicy > { - typedef ThreadingModel< SmallObject > 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 MyAllocator; - + 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::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::Instance().Deallocate(p, size); -#else - ::operator delete(p); -#endif - } 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 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 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 //////////////////////////////////////////////////////////////////////////////// // Change log: // 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_ + +// $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. +// diff --git a/src/SmallObj.cpp b/src/SmallObj.cpp index ebbbd87..f480602 100644 --- a/src/SmallObj.cpp +++ b/src/SmallObj.cpp @@ -13,29 +13,122 @@ // without express or implied warranty. //////////////////////////////////////////////////////////////////////////////// -// Last update: March 20, 2001 +// $Header$ + #include "SmallObj.h" + #include +#include #include #include + 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 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 // 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(blocks > 0); // Overflow check - assert((blockSize * blocks) / blockSize == blocks); - - pData_ = new unsigned char[blockSize * blocks]; - Reset(blockSize, blocks); + const std::size_t allocSize = blockSize * blocks; + assert( allocSize / blockSize == blocks); + +#ifdef USE_NEW_TO_ALLOCATE + // 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() { - delete[] pData_; + assert( NULL != pData_ ); +#ifdef USE_NEW_TO_ALLOCATE + delete [] pData_; +#else + ::free( static_cast< void * >( pData_ ) ); +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -78,16 +176,14 @@ void FixedAllocator::Chunk::Release() void* FixedAllocator::Chunk::Allocate(std::size_t blockSize) { - if (!blocksAvailable_) return 0; - + if ( IsFilled() ) return NULL; + assert((firstAvailableBlock_ * blockSize) / blockSize == firstAvailableBlock_); - - unsigned char* pResult = - pData_ + (firstAvailableBlock_ * blockSize); + unsigned char * pResult = pData_ + (firstAvailableBlock_ * blockSize); firstAvailableBlock_ = *pResult; --blocksAvailable_; - + return pResult; } @@ -118,52 +214,12 @@ void FixedAllocator::Chunk::Deallocate(void* p, std::size_t blockSize) // Creates a FixedAllocator object of a fixed block size //////////////////////////////////////////////////////////////////////////////// -FixedAllocator::FixedAllocator(std::size_t blockSize) - : blockSize_(blockSize) - , allocChunk_(0) - , deallocChunk_(0) +FixedAllocator::FixedAllocator() + : blockSize_( 0 ) + , allocChunk_( NULL ) + , 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(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() { - if (prev_ != this) - { - prev_->next_ = next_; - next_->prev_ = prev_; - return; - } - - assert(prev_ == next_); - Chunks::iterator i = chunks_.begin(); - for (; i != chunks_.end(); ++i) - { - assert(i->blocksAvailable_ == numBlocks_); + for ( ChunkIter i( chunks_.begin() ); i != chunks_.end(); ++i ) 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; - - swap(blockSize_, rhs.blockSize_); - swap(numBlocks_, rhs.numBlocks_); - chunks_.swap(rhs.chunks_); - swap(allocChunk_, rhs.allocChunk_); - swap(deallocChunk_, rhs.deallocChunk_); + assert( blockSize > 0 ); + assert( pageSize >= blockSize ); + blockSize_ = blockSize; + + std::size_t numBlocks = pageSize / blockSize; + if (numBlocks > UCHAR_MAX) numBlocks = UCHAR_MAX; + else if ( numBlocks < 8 ) numBlocks = 8; + + numBlocks_ = static_cast(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 //////////////////////////////////////////////////////////////////////////////// -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(); - for (;; ++i) + if ( NULL != emptyChunk_ ) { - if (i == chunks_.end()) + allocChunk_ = emptyChunk_; + emptyChunk_ = NULL; + } + else + { + for ( ChunkIter i( chunks_.begin() ); ; ++i ) { - // Initialize - chunks_.reserve(chunks_.size() + 1); - Chunk newChunk; - newChunk.Init(blockSize_, numBlocks_); - chunks_.push_back(newChunk); - allocChunk_ = &chunks_.back(); - deallocChunk_ = &chunks_.front(); - break; - } - if (i->blocksAvailable_ > 0) - { - allocChunk_ = &*i; - break; + if ( chunks_.end() == i ) + { + if ( !MakeNewChunk() ) + return NULL; + break; + } + if ( !i->IsFilled() ) + { + allocChunk_ = &*i; + break; + } } } } - assert(allocChunk_ != 0); - assert(allocChunk_->blocksAvailable_ > 0); - - return allocChunk_->Allocate(blockSize_); + else if ( allocChunk_ == emptyChunk_) + // detach emptyChunk_ from allocChunk_, because after + // calling allocChunk_->Allocate(blockSize_); the chunk + // isn't any more empty + emptyChunk_ = NULL; + + 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) //////////////////////////////////////////////////////////////////////////////// -void FixedAllocator::Deallocate(void* p) +bool FixedAllocator::Deallocate( void * p, bool doChecks ) { - assert(!chunks_.empty()); - assert(&chunks_.front() <= deallocChunk_); - assert(&chunks_.back() >= deallocChunk_); - - deallocChunk_ = VicinityFind(p); - assert(deallocChunk_); + if ( doChecks ) + { + assert(!chunks_.empty()); + assert(&chunks_.front() <= deallocChunk_); + assert(&chunks_.back() >= deallocChunk_); + assert( &chunks_.front() <= allocChunk_ ); + assert( &chunks_.back() >= allocChunk_ ); + } + Chunk * foundChunk = VicinityFind( p ); + if ( doChecks ) + { + assert( NULL != foundChunk ); + } + else if ( NULL == foundChunk ) + return false; + + deallocChunk_ = foundChunk; DoDeallocate(p); + return true; } //////////////////////////////////////////////////////////////////////////////// @@ -262,44 +367,47 @@ void FixedAllocator::Deallocate(void* p) // 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_); + unsigned char * pc = static_cast< unsigned char * >( p ); const std::size_t chunkLength = numBlocks_ * blockSize_; Chunk* lo = deallocChunk_; Chunk* hi = deallocChunk_ + 1; Chunk* loBound = &chunks_.front(); Chunk* hiBound = &chunks_.back() + 1; - - // Special case: deallocChunk_ is the last in the array - if (hi == hiBound) hi = 0; + + // Special case: deallocChunk_ is the last in the array + if (hi == hiBound) hi = NULL; for (;;) { 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; } - + 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) { - assert(deallocChunk_->pData_ <= p); - assert(deallocChunk_->pData_ + numBlocks_ * blockSize_ > p); + assert( deallocChunk_->HasBlock( static_cast< unsigned char * >( 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 deallocChunk_->Deallocate(p, blockSize_); - if (deallocChunk_->blocksAvailable_ == numBlocks_) + if ( deallocChunk_->HasAvailable( numBlocks_ ) ) { - // deallocChunk_ is completely free, should we release it? - - Chunk& lastChunk = chunks_.back(); - - if (&lastChunk == deallocChunk_) + assert( emptyChunk_ != deallocChunk_ ); + // deallocChunk_ is empty, but a Chunk is only released if there are 2 + // empty chunks. Since emptyChunk_ may only point to a previously + // cleared Chunk, if it points to something else besides deallocChunk_, + // then FixedAllocator currently has 2 empty Chunks. + if ( NULL != emptyChunk_ ) { - // check if we have two last chunks empty - - if (chunks_.size() > 1 && - deallocChunk_[-1].blocksAvailable_ == numBlocks_) - { - // Two free chunks, discard the last one - lastChunk.Release(); - chunks_.pop_back(); - allocChunk_ = deallocChunk_ = &chunks_.front(); - } - return; - } - - if (lastChunk.blocksAvailable_ == numBlocks_) - { - // Two free blocks, discard one - lastChunk.Release(); + // If last Chunk is empty, just change what deallocChunk_ + // points to, and release the last. Otherwise, swap an empty + // Chunk with the last, and then release it. + Chunk * lastChunk = &chunks_.back(); + if ( lastChunk == deallocChunk_ ) + deallocChunk_ = emptyChunk_; + else if ( lastChunk != emptyChunk_ ) + std::swap( *emptyChunk_, *lastChunk ); + assert( lastChunk->HasAvailable( numBlocks_ ) ); + lastChunk->Release(); chunks_.pop_back(); allocChunk_ = deallocChunk_; } - else - { - // move the empty chunk to the end - std::swap(*deallocChunk_, lastChunk); - allocChunk_ = &chunks_.back(); - } + emptyChunk_ = deallocChunk_; } + + // 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 -// Creates an allocator for small objects given chunk size and maximum 'small' -// object size +// Creates a SmallObjAllocator, and all the FixedAllocators within it. Each +// FixedAllocator is then initialized to use the correct Chunk size. //////////////////////////////////////////////////////////////////////////////// -SmallObjAllocator::SmallObjAllocator( - std::size_t chunkSize, - std::size_t maxObjectSize) - : pLastAlloc_(0), pLastDealloc_(0) - , chunkSize_(chunkSize), maxObjectSize_(maxObjectSize) -{ +SmallObjAllocator::SmallObjAllocator( std::size_t pageSize, + std::size_t maxObjectSize, std::size_t objectAlignSize ) : + pool_( NULL ), + maxSmallObjectSize_( 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 -struct CompareFixedAllocatorSize - : std::binary_function +SmallObjAllocator::~SmallObjAllocator( void ) { - bool operator()(const FixedAllocator &x, std::size_t numBytes) const - { - return x.BlockSize() < numBytes; - } -}; - -} // anoymous namespace + delete [] pool_; +} //////////////////////////////////////////////////////////////////////////////// // SmallObjAllocator::Allocate -// Allocates 'numBytes' memory -// Uses an internal pool of FixedAllocator objects for small objects +// Handles request to allocate numBytes for 1 object. +// 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 (pLastAlloc_ && pLastAlloc_->BlockSize() == numBytes) + if ( numBytes > GetMaxObjectSize() ) + return DefaultAllocator( numBytes, doThrow ); + + 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, - CompareFixedAllocatorSize()); - if (i == pool_.end() || i->BlockSize() != numBytes) - { - i = pool_.insert(i, FixedAllocator(numBytes)); - pLastDealloc_ = &*pool_.begin(); - } - pLastAlloc_ = &*i; - return pLastAlloc_->Allocate(); + return place; } //////////////////////////////////////////////////////////////////////////////// // SmallObjAllocator::Deallocate -// Deallocates memory previously allocated with Allocate -// (undefined behavior if you pass any other pointer) +// Handles request to deallocate numBytes for 1 object. +// 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 (pLastDealloc_ && pLastDealloc_->BlockSize() == numBytes) + if ( NULL == p ) return; + if ( numBytes > GetMaxObjectSize() ) { - pLastDealloc_->Deallocate(p); + DefaultDeallocator( p ); return; } - Pool::iterator i = std::lower_bound(pool_.begin(), pool_.end(), numBytes, - CompareFixedAllocatorSize()); - assert(i != pool_.end()); - assert(i->BlockSize() == numBytes); - pLastDealloc_ = &*i; - pLastDealloc_->Deallocate(p); + 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() ); + const bool found = allocator.Deallocate( p, true ); + assert( found ); } +} // end namespace Loki + //////////////////////////////////////////////////////////////////////////////// // Change log: // March 20: fix exception safety issue in FixedAllocator::Allocate // (thanks to Chris Udazvinis for pointing that out) // June 20, 2001: ported by Nick Thurn to gcc 2.95.3. Kudos, Nick!!! // 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. +//