Moved Chunk out of FixedAllocator class so I could improve efficiency for
SmallObjAllocator::Deallocate. git-svn-id: svn://svn.code.sf.net/p/loki-lib/code/trunk@272 7ec92016-0320-0410-acc4-a06ded1c099a
This commit is contained in:
parent
e529d13e1b
commit
abe707a047
1 changed files with 124 additions and 120 deletions
244
src/SmallObj.cpp
244
src/SmallObj.cpp
|
@ -30,6 +30,88 @@ using namespace Loki;
|
||||||
namespace Loki
|
namespace Loki
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/** @struct Chunk Contains info about each allocated Chunk.
|
||||||
|
This is a POD-style struct with value-semantics.
|
||||||
|
|
||||||
|
@par Minimal Interface
|
||||||
|
For the sake of runtime efficiency, no constructor, destructor, or
|
||||||
|
copy-assignment operator is defined. The inline functions made by the
|
||||||
|
compiler should be sufficient, and perhaps faster than hand-crafted
|
||||||
|
functions. The lack of these functions allows vector to create and copy
|
||||||
|
Chunks as needed without overhead. The Init and Release functions do
|
||||||
|
what the default constructor and destructor would do. A Chunk is not in
|
||||||
|
a usable state after it is constructed and before calling Init. Nor is
|
||||||
|
a Chunk usable after Release is called, but before the destructor.
|
||||||
|
|
||||||
|
@par Efficiency
|
||||||
|
Down near the lowest level of the allocator, runtime efficiencies trump
|
||||||
|
almost all other considerations. Each function does the minimum required
|
||||||
|
of it. All functions should execute in constant time to prevent higher-
|
||||||
|
level code from unwittingly using a version of Shlemiel the Painter's
|
||||||
|
Algorithm.
|
||||||
|
|
||||||
|
@par Stealth Indexes
|
||||||
|
The first char of each empty block contains the index of the next empty
|
||||||
|
block. These stealth indexes form a singly-linked list within the blocks.
|
||||||
|
A Chunk is corrupt if this singly-linked list has a loop or is shorter
|
||||||
|
than blocksAvailable_. Much of the allocator's time and space efficiency
|
||||||
|
comes from how these stealth indexes are implemented.
|
||||||
|
*/
|
||||||
|
struct Chunk
|
||||||
|
{
|
||||||
|
/** Initializes a just-constructed Chunk.
|
||||||
|
@param blockSize Number of bytes per block.
|
||||||
|
@param blocks Number of blocks per Chunk.
|
||||||
|
@return True for success, false for failure.
|
||||||
|
*/
|
||||||
|
bool Init( std::size_t blockSize, unsigned char blocks );
|
||||||
|
|
||||||
|
/** Allocate a block within the Chunk. Complexity is always O(1), and
|
||||||
|
this will never throw. Does not actually "allocate" by calling
|
||||||
|
malloc, new, or any other function, but merely adjusts some internal
|
||||||
|
indexes to indicate an already allocated block is no longer available.
|
||||||
|
@return Pointer to block within Chunk.
|
||||||
|
*/
|
||||||
|
void * Allocate( std::size_t blockSize );
|
||||||
|
|
||||||
|
/** Deallocate a block within the Chunk. Complexity is always O(1), and
|
||||||
|
this will never throw. For efficiency, this assumes the address is
|
||||||
|
within the block and aligned along the correct byte boundary. An
|
||||||
|
assertion checks the alignment, and a call to HasBlock is done from
|
||||||
|
within VicinityFind. Does not actually "deallocate" by calling free,
|
||||||
|
delete, or other function, but merely adjusts some internal indexes to
|
||||||
|
indicate a block is now available.
|
||||||
|
*/
|
||||||
|
void Deallocate( void * p, std::size_t blockSize );
|
||||||
|
|
||||||
|
/** Resets the Chunk back to pristine values. The available count is
|
||||||
|
set back to zero, and the first available index is set to the zeroth
|
||||||
|
block. The stealth indexes inside each block are set to point to the
|
||||||
|
next block. This assumes the Chunk's data was already using Init.
|
||||||
|
*/
|
||||||
|
void Reset( std::size_t blockSize, unsigned char blocks );
|
||||||
|
|
||||||
|
/// Releases the allocated block of memory.
|
||||||
|
void Release();
|
||||||
|
|
||||||
|
/// Returns true if block at address P is inside this Chunk.
|
||||||
|
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_ ); }
|
||||||
|
|
||||||
|
/// Pointer to array of allocated blocks.
|
||||||
|
unsigned char * pData_;
|
||||||
|
/// Index of first empty block.
|
||||||
|
unsigned char firstAvailableBlock_;
|
||||||
|
/// Count of empty blocks.
|
||||||
|
unsigned char blocksAvailable_;
|
||||||
|
};
|
||||||
|
|
||||||
/** @class FixedAllocator
|
/** @class FixedAllocator
|
||||||
Offers services for allocating fixed-sized objects. It has a container
|
Offers services for allocating fixed-sized objects. It has a container
|
||||||
of "containers" of fixed-size blocks. The outer container has all the
|
of "containers" of fixed-size blocks. The outer container has all the
|
||||||
|
@ -50,91 +132,6 @@ namespace Loki
|
||||||
class FixedAllocator
|
class FixedAllocator
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
/** @struct Chunk Contains info about each allocated Chunk.
|
|
||||||
This is a POD-style struct with value-semantics.
|
|
||||||
|
|
||||||
@par Minimal Interface
|
|
||||||
For the sake of runtime efficiency, no constructor, destructor, or
|
|
||||||
copy-assignment operator is defined. The inline functions made by the
|
|
||||||
compiler should be sufficient, and perhaps faster than hand-crafted
|
|
||||||
functions. The lack of these functions allows vector to create and
|
|
||||||
copy Chunks as needed without overhead. The Init and Release
|
|
||||||
functions do what the default constructor and destructor would do. A
|
|
||||||
Chunk is not in a usable state after it is constructed and before
|
|
||||||
calling Init. Nor is a Chunk usable after Release is called, but
|
|
||||||
before the destructor.
|
|
||||||
|
|
||||||
@par Efficiency
|
|
||||||
Down near the lowest level of the allocator, runtime efficiencies
|
|
||||||
trump almost all other considerations. Each function does the minimum
|
|
||||||
required of it. All functions should execute in constant time to
|
|
||||||
prevent higher-level code from unwittingly using a version of
|
|
||||||
Shlemiel the Painter's Algorithm.
|
|
||||||
|
|
||||||
@par Stealth Indexes
|
|
||||||
The first char of each empty block contains the index of the next
|
|
||||||
empty block. These stealth indexes form a singly-linked list within
|
|
||||||
the blocks. A Chunk is corrupt if this singly-linked list has a loop
|
|
||||||
or is shorter than blocksAvailable_. Much of the allocator's time and
|
|
||||||
space efficiency comes from how these stealth indexes are implemented.
|
|
||||||
*/
|
|
||||||
struct Chunk
|
|
||||||
{
|
|
||||||
/** Initializes a just-constructed Chunk.
|
|
||||||
@param blockSize Number of bytes per block.
|
|
||||||
@param blocks Number of blocks per Chunk.
|
|
||||||
@return True for success, false for failure.
|
|
||||||
*/
|
|
||||||
bool Init( std::size_t blockSize, unsigned char blocks );
|
|
||||||
|
|
||||||
/** Allocate a block within the Chunk. Complexity is always O(1),
|
|
||||||
and this will never throw. Does not actually "allocate" by
|
|
||||||
calling malloc, new, or any other function, but merely adjusts
|
|
||||||
some internal indexes to indicate an already allocated block is
|
|
||||||
no longer available.
|
|
||||||
@return Pointer to block within Chunk.
|
|
||||||
*/
|
|
||||||
void * Allocate( std::size_t blockSize );
|
|
||||||
|
|
||||||
/** Deallocate a block within the Chunk. Complexity is always
|
|
||||||
O(1), and this will never throw. For efficiency, this assumes
|
|
||||||
the address is within the block and aligned along the correct
|
|
||||||
byte boundary. An assertion checks the alignment, and a call to
|
|
||||||
HasBlock is done from within VicinityFind. Does not actually
|
|
||||||
"deallocate" by calling free, delete, or any other function, but
|
|
||||||
merely adjusts some internal indexes to indicate a block is no
|
|
||||||
longer available.
|
|
||||||
*/
|
|
||||||
void Deallocate( void * p, std::size_t blockSize );
|
|
||||||
|
|
||||||
/** Resets the Chunk back to pristine values. The available count
|
|
||||||
is set back to zero, and the first available index is set to the
|
|
||||||
zeroth block. The stealth indexes inside each block are set to
|
|
||||||
point to the next block. This assumes the Chunk's data was already
|
|
||||||
using Init.
|
|
||||||
*/
|
|
||||||
void Reset( std::size_t blockSize, unsigned char blocks );
|
|
||||||
|
|
||||||
/// Releases the allocated block of memory.
|
|
||||||
void Release();
|
|
||||||
|
|
||||||
/// Returns true if block at address P is inside this Chunk.
|
|
||||||
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_ ); }
|
|
||||||
|
|
||||||
/// Pointer to array of allocated blocks.
|
|
||||||
unsigned char * pData_;
|
|
||||||
/// Index of first empty block.
|
|
||||||
unsigned char firstAvailableBlock_;
|
|
||||||
/// Count of empty blocks.
|
|
||||||
unsigned char blocksAvailable_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Deallocates the block at address p, and then handles the internal
|
/** Deallocates the block at address p, and then handles the internal
|
||||||
bookkeeping needed to maintain class invariants. This assumes that
|
bookkeeping needed to maintain class invariants. This assumes that
|
||||||
|
@ -209,7 +206,7 @@ namespace Loki
|
||||||
that SmallObjAllocator can call the default deallocator. If the
|
that SmallObjAllocator can call the default deallocator. If the
|
||||||
block was found, this returns true.
|
block was found, this returns true.
|
||||||
*/
|
*/
|
||||||
bool Deallocate( void * p, bool doChecks );
|
bool Deallocate( void * p, Chunk * hint );
|
||||||
|
|
||||||
/// Returns block size with which the FixedAllocator was initialized.
|
/// Returns block size with which the FixedAllocator was initialized.
|
||||||
inline std::size_t BlockSize() const { return blockSize_; }
|
inline std::size_t BlockSize() const { return blockSize_; }
|
||||||
|
@ -228,14 +225,19 @@ namespace Loki
|
||||||
this FixedAllocator. Complexity is O(C) where C is the total number
|
this FixedAllocator. Complexity is O(C) where C is the total number
|
||||||
of Chunks - empty or used.
|
of Chunks - empty or used.
|
||||||
*/
|
*/
|
||||||
bool HasBlock( void * p ) const;
|
const Chunk * HasBlock( void * p ) const;
|
||||||
|
inline Chunk * HasBlock( void * p )
|
||||||
|
{
|
||||||
|
return const_cast< Chunk * >(
|
||||||
|
const_cast< const FixedAllocator * >( this )->HasBlock( p ) );
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// FixedAllocator::Chunk::Init ------------------------------------------------
|
// Chunk::Init ----------------------------------------------------------------
|
||||||
|
|
||||||
bool FixedAllocator::Chunk::Init( std::size_t blockSize, unsigned char blocks )
|
bool Chunk::Init( std::size_t blockSize, unsigned char blocks )
|
||||||
{
|
{
|
||||||
assert(blockSize > 0);
|
assert(blockSize > 0);
|
||||||
assert(blocks > 0);
|
assert(blocks > 0);
|
||||||
|
@ -258,9 +260,9 @@ bool FixedAllocator::Chunk::Init( std::size_t blockSize, unsigned char blocks )
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedAllocator::Chunk::Reset -----------------------------------------------
|
// Chunk::Reset ---------------------------------------------------------------
|
||||||
|
|
||||||
void FixedAllocator::Chunk::Reset(std::size_t blockSize, unsigned char blocks)
|
void Chunk::Reset(std::size_t blockSize, unsigned char blocks)
|
||||||
{
|
{
|
||||||
assert(blockSize > 0);
|
assert(blockSize > 0);
|
||||||
assert(blocks > 0);
|
assert(blocks > 0);
|
||||||
|
@ -277,9 +279,9 @@ void FixedAllocator::Chunk::Reset(std::size_t blockSize, unsigned char blocks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedAllocator::Chunk::Release ---------------------------------------------
|
// Chunk::Release -------------------------------------------------------------
|
||||||
|
|
||||||
void FixedAllocator::Chunk::Release()
|
void Chunk::Release()
|
||||||
{
|
{
|
||||||
assert( NULL != pData_ );
|
assert( NULL != pData_ );
|
||||||
#ifdef USE_NEW_TO_ALLOCATE
|
#ifdef USE_NEW_TO_ALLOCATE
|
||||||
|
@ -289,9 +291,9 @@ void FixedAllocator::Chunk::Release()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedAllocator::Chunk::Allocate --------------------------------------------
|
// Chunk::Allocate ------------------------------------------------------------
|
||||||
|
|
||||||
void* FixedAllocator::Chunk::Allocate(std::size_t blockSize)
|
void* Chunk::Allocate(std::size_t blockSize)
|
||||||
{
|
{
|
||||||
if ( IsFilled() ) return NULL;
|
if ( IsFilled() ) return NULL;
|
||||||
|
|
||||||
|
@ -304,9 +306,9 @@ void* FixedAllocator::Chunk::Allocate(std::size_t blockSize)
|
||||||
return pResult;
|
return pResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedAllocator::Chunk::Deallocate ------------------------------------------
|
// Chunk::Deallocate ----------------------------------------------------------
|
||||||
|
|
||||||
void FixedAllocator::Chunk::Deallocate(void* p, std::size_t blockSize)
|
void Chunk::Deallocate(void* p, std::size_t blockSize)
|
||||||
{
|
{
|
||||||
assert(p >= pData_);
|
assert(p >= pData_);
|
||||||
|
|
||||||
|
@ -380,7 +382,7 @@ std::size_t FixedAllocator::CountEmptyChunks( void ) const
|
||||||
|
|
||||||
// FixedAllocator::HasBlock ---------------------------------------------------
|
// FixedAllocator::HasBlock ---------------------------------------------------
|
||||||
|
|
||||||
bool FixedAllocator::HasBlock( void * p ) const
|
const Chunk * FixedAllocator::HasBlock( void * p ) const
|
||||||
{
|
{
|
||||||
const std::size_t chunkLength = numBlocks_ * blockSize_;
|
const std::size_t chunkLength = numBlocks_ * blockSize_;
|
||||||
unsigned char * pc = static_cast< unsigned char * >( p );
|
unsigned char * pc = static_cast< unsigned char * >( p );
|
||||||
|
@ -388,9 +390,9 @@ bool FixedAllocator::HasBlock( void * p ) const
|
||||||
{
|
{
|
||||||
const Chunk & chunk = *it;
|
const Chunk & chunk = *it;
|
||||||
if ( chunk.HasBlock( pc, chunkLength ) )
|
if ( chunk.HasBlock( pc, chunkLength ) )
|
||||||
return true;
|
return &chunk;
|
||||||
}
|
}
|
||||||
return false;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixedAllocator::TrimEmptyChunk ---------------------------------------------
|
// FixedAllocator::TrimEmptyChunk ---------------------------------------------
|
||||||
|
@ -521,26 +523,21 @@ void * FixedAllocator::Allocate( void )
|
||||||
|
|
||||||
// FixedAllocator::Deallocate -------------------------------------------------
|
// FixedAllocator::Deallocate -------------------------------------------------
|
||||||
|
|
||||||
bool FixedAllocator::Deallocate( void * p, bool doChecks )
|
bool FixedAllocator::Deallocate( void * p, Chunk * hint )
|
||||||
{
|
{
|
||||||
if ( doChecks )
|
assert(!chunks_.empty());
|
||||||
{
|
assert(&chunks_.front() <= deallocChunk_);
|
||||||
assert(!chunks_.empty());
|
assert(&chunks_.back() >= deallocChunk_);
|
||||||
assert(&chunks_.front() <= deallocChunk_);
|
assert( &chunks_.front() <= allocChunk_ );
|
||||||
assert(&chunks_.back() >= deallocChunk_);
|
assert( &chunks_.back() >= allocChunk_ );
|
||||||
assert( &chunks_.front() <= allocChunk_ );
|
assert( CountEmptyChunks() < 2 );
|
||||||
assert( &chunks_.back() >= allocChunk_ );
|
|
||||||
assert( CountEmptyChunks() < 2 );
|
|
||||||
}
|
|
||||||
|
|
||||||
Chunk * foundChunk = VicinityFind( p );
|
Chunk * foundChunk = ( NULL == hint ) ? VicinityFind( p ) : hint;
|
||||||
if ( doChecks )
|
if ( NULL == foundChunk )
|
||||||
{
|
|
||||||
assert( NULL != foundChunk );
|
|
||||||
}
|
|
||||||
else if ( NULL == foundChunk )
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
assert( foundChunk->HasBlock( static_cast< unsigned char * >( p ),
|
||||||
|
numBlocks_ * blockSize_ ) );
|
||||||
deallocChunk_ = foundChunk;
|
deallocChunk_ = foundChunk;
|
||||||
DoDeallocate(p);
|
DoDeallocate(p);
|
||||||
assert( CountEmptyChunks() < 2 );
|
assert( CountEmptyChunks() < 2 );
|
||||||
|
@ -550,7 +547,7 @@ bool FixedAllocator::Deallocate( void * p, bool doChecks )
|
||||||
|
|
||||||
// FixedAllocator::VicinityFind -----------------------------------------------
|
// FixedAllocator::VicinityFind -----------------------------------------------
|
||||||
|
|
||||||
FixedAllocator::Chunk * FixedAllocator::VicinityFind( void * p ) const
|
Chunk * FixedAllocator::VicinityFind( void * p ) const
|
||||||
{
|
{
|
||||||
if ( chunks_.empty() ) return NULL;
|
if ( chunks_.empty() ) return NULL;
|
||||||
assert(deallocChunk_);
|
assert(deallocChunk_);
|
||||||
|
@ -767,7 +764,7 @@ void SmallObjAllocator::Deallocate( void * p, std::size_t numBytes )
|
||||||
FixedAllocator & allocator = pool_[ index ];
|
FixedAllocator & allocator = pool_[ index ];
|
||||||
assert( allocator.BlockSize() >= numBytes );
|
assert( allocator.BlockSize() >= numBytes );
|
||||||
assert( allocator.BlockSize() < numBytes + GetAlignment() );
|
assert( allocator.BlockSize() < numBytes + GetAlignment() );
|
||||||
const bool found = allocator.Deallocate( p, true );
|
const bool found = allocator.Deallocate( p, NULL );
|
||||||
assert( found );
|
assert( found );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,10 +776,12 @@ void SmallObjAllocator::Deallocate( void * p )
|
||||||
assert( NULL != pool_ );
|
assert( NULL != pool_ );
|
||||||
FixedAllocator * pAllocator = NULL;
|
FixedAllocator * pAllocator = NULL;
|
||||||
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
|
const std::size_t allocCount = GetOffset( GetMaxObjectSize(), GetAlignment() );
|
||||||
|
Chunk * chunk = NULL;
|
||||||
|
|
||||||
for ( std::size_t ii = 0; ii < allocCount; ++ii )
|
for ( std::size_t ii = 0; ii < allocCount; ++ii )
|
||||||
{
|
{
|
||||||
if ( pool_[ ii ].HasBlock( p ) )
|
chunk = pool_[ ii ].HasBlock( p );
|
||||||
|
if ( NULL != chunk )
|
||||||
{
|
{
|
||||||
pAllocator = &pool_[ ii ];
|
pAllocator = &pool_[ ii ];
|
||||||
break;
|
break;
|
||||||
|
@ -794,7 +793,8 @@ void SmallObjAllocator::Deallocate( void * p )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool found = pAllocator->Deallocate( p, true );
|
assert( NULL != chunk );
|
||||||
|
const bool found = pAllocator->Deallocate( p, chunk );
|
||||||
assert( found );
|
assert( found );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -811,6 +811,10 @@ void SmallObjAllocator::Deallocate( void * p )
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// $Log$
|
// $Log$
|
||||||
|
// Revision 1.7 2005/09/27 00:40:30 rich_sposato
|
||||||
|
// Moved Chunk out of FixedAllocator class so I could improve efficiency for
|
||||||
|
// SmallObjAllocator::Deallocate.
|
||||||
|
//
|
||||||
// Revision 1.6 2005/09/26 21:38:54 rich_sposato
|
// Revision 1.6 2005/09/26 21:38:54 rich_sposato
|
||||||
// Changed include path to be direct instead of relying upon project settings.
|
// Changed include path to be direct instead of relying upon project settings.
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue