|
|
// Ruler
// 1 2 3 4 5 6 7 8
//345678901234567890123456789012345678901234567890123456789012345678901234567890
/********************************************************************/ /* */ /* The standard layout. */ /* */ /* The standard layout for 'cpp' files for this code is as */ /* follows: */ /* */ /* 1. Include files. */ /* 2. Constants local to the class. */ /* 3. Data structures local to the class. */ /* 4. Data initializations. */ /* 5. Static functions. */ /* 6. Class functions. */ /* */ /* The constructor is typically the first function, class */ /* member functions appear in alphabetical order with the */ /* destructor appearing at the end of the file. Any section */ /* or function that is not required is simply omitted. */ /* */ /********************************************************************/
#include "HeapPCH.hpp"
#include "Bucket.hpp"
#include "Cache.hpp"
#include "Heap.hpp"
/********************************************************************/ /* */ /* Constants local to the class. */ /* */ /* The constants supplied here relate the compuation of the */ /* current active page address range. */ /* */ /********************************************************************/
CONST SBIT32 HighestAddress = -1;
/********************************************************************/ /* */ /* Class constructor. */ /* */ /* Create a new allocation bucket and prepare it for use. */ /* We need to be sure to carefully check everything we have */ /* been supplied as it has come indirectly from the user. */ /* */ /********************************************************************/
BUCKET::BUCKET ( SBIT32 NewAllocationSize, SBIT32 NewChunkSize, SBIT32 NewPageSize ) { //
// We want to make sure that the bucket configuration
// appears to make basic sense. If not we have no
// alternative than to throw an expection.
//
if ( (NewAllocationSize > 0) && (NewChunkSize >= NewAllocationSize) && (NewChunkSize <= NewPageSize) && PowerOfTwo( NewPageSize ) ) { //
// Create the bucket and prepare it for use.
// Pre-compute any information we can here to
// save work later.
//
AllocationSize = NewAllocationSize; ChunkSize = NewChunkSize; PageSize = NewPageSize;
ActivePages = 0; AllocationShift = 0; ChunkShift = 0;
//
// Compute the optimization level from the available
// bucket information. The highest level means
// everything is a power of two (just shifts - yipee !!).
// The next means no chunks (i.e. simple scaling), The
// next means simple chunks (i.e. complex scaling).
// The final alternative is to horrid to discuss.
//
if ( ConvertDivideToShift( AllocationSize,& AllocationShift ) ) { //
// If we are not using chunking we can skip the
// extra compuation that is needed. We can tell
// this is the case if the chunk size and the page
// size match or if the chunk size is a multiple
// of the allocation size.
//
if ( (ChunkSize == PageSize) || ((ChunkSize % AllocationSize) == 0) ) { //
// The best case is when we can use shifts
// instead of multiply and divide.
//
ComputeAddressFunction = ComputeAddressBestCase; ComputeOffsetFunction = ComputeOffsetBestCase; } else { //
// When we have chunks we have to do more
// work invloving additional multiples and
// divides. Nonetheless, we try to minimize
// these as far as possible.
//
if ( ConvertDivideToShift( ChunkSize,& ChunkShift ) ) { //
// We have managed to replace some of the
// multiplies and divides with shifts.
//
ComputeAddressFunction = ComputeAddressPoorCase; ComputeOffsetFunction = ComputeOffsetPoorCase; } else { //
// We are unable to optimize this case.
// These calls will really hurt.
//
ComputeAddressFunction = ComputeAddressWorstCase; ComputeOffsetFunction = ComputeOffsetWorstCase; } } } else { //
// If we are not using chunking we can skip the
// extra compuation that is needed. We can tell
// this is the case if the chunk size and the page
// size match or if the chunk size is a multiple
// of the allocation size.
//
if ( (ChunkSize == PageSize) || ((ChunkSize % AllocationSize) == 0) ) { //
// A good case is when we can use a few
// simple multiplies and divides to do simple
// scaling.
//
ComputeAddressFunction = ComputeAddressGoodCase; ComputeOffsetFunction = ComputeOffsetGoodCase; } else { //
// When we have chunks we have to do more
// work invloving additional multiples and
// divides. Nonetheless, we try to minimize
// these as far as possible.
//
if ( ConvertDivideToShift( ChunkSize,& ChunkShift ) ) { //
// We have managed to replace some of the
// multiplies and divides with shifts.
//
ComputeAddressFunction = ComputeAddressPoorCase; ComputeOffsetFunction = ComputeOffsetPoorCase; } else { //
// We are unable to optimize this case.
// These calls will really hurt.
//
ComputeAddressFunction = ComputeAddressWorstCase; ComputeOffsetFunction = ComputeOffsetWorstCase; } } }
//
// Compute all the information that will be
// needed later to describe the allocation
// pages.
//
NumberOfElements = ((SBIT16) ((PageSize / ChunkSize) * (ChunkSize / AllocationSize))); SizeOfChunks = (SBIT16) ((SBIT16) (ChunkSize / AllocationSize)); SizeOfElements = (SBIT16) ((SBIT16) (((NumberOfElements-1) / OverheadBitsPerWord) + 1)); SizeKey = NoSizeKey; } else { Failure( "Configuration in constructor for BUCKET" ); } }
/********************************************************************/ /* */ /* Compute the allocation address. */ /* */ /* Compute the allocation address given the page address and */ /* the vector offset in the page. */ /* */ /********************************************************************/
VOID *BUCKET::ComputeAddressBestCase( CHAR *Address,SBIT32 Offset ) { return ((VOID*) (Address + (Offset << AllocationShift))); }
/********************************************************************/ /* */ /* Compute the allocation address. */ /* */ /* Compute the allocation address given the page address and */ /* the vector offset in the page. */ /* */ /********************************************************************/
VOID *BUCKET::ComputeAddressGoodCase( CHAR *Address,SBIT32 Offset ) { return ((VOID*) (Address + (Offset * AllocationSize))); }
/********************************************************************/ /* */ /* Compute the allocation address. */ /* */ /* Compute the allocation address given the page address and */ /* the vector offset in the page. */ /* */ /********************************************************************/
VOID *BUCKET::ComputeAddressPoorCase( CHAR *Address,SBIT32 Offset ) { REGISTER SBIT32 ChunkNumber = (Offset / SizeOfChunks); REGISTER SBIT32 ChunkOffset = (ChunkNumber * SizeOfChunks); REGISTER SBIT32 AllocationNumber = (Offset - ChunkOffset);
return ((VOID*) ( Address + (ChunkNumber << ChunkShift) + (AllocationNumber * AllocationSize) ) ); }
/********************************************************************/ /* */ /* Compute the allocation address. */ /* */ /* Compute the allocation address given the page address and */ /* the vector offset in the page. */ /* */ /********************************************************************/
VOID *BUCKET::ComputeAddressWorstCase( CHAR *Address,SBIT32 Offset ) { REGISTER SBIT32 ChunkNumber = (Offset / SizeOfChunks); REGISTER SBIT32 ChunkOffset = (ChunkNumber * SizeOfChunks); REGISTER SBIT32 AllocationNumber = (Offset - ChunkOffset);
return ((VOID*) ( Address + (ChunkNumber * ChunkSize) + (AllocationNumber * AllocationSize) ) ); }
/********************************************************************/ /* */ /* Compute the bit vector offset. */ /* */ /* Compute the bit vector offset given the address of the */ /* memory allocation in the page. */ /* */ /********************************************************************/
SBIT32 BUCKET::ComputeOffsetBestCase( SBIT32 Displacement,BOOLEAN *Found ) { REGISTER SBIT32 ArrayOffset = (Displacement >> AllocationShift);
(*Found) = (Displacement == (ArrayOffset << AllocationShift)); #ifdef DEBUGGING
if ( ArrayOffset >= NumberOfElements ) { Failure( "Array offset in ComputeOffsetBestCase" ); } #endif
return ArrayOffset; }
/********************************************************************/ /* */ /* Compute the bit vector offset. */ /* */ /* Compute the bit vector offset given the address of the */ /* memory allocation in the page. */ /* */ /********************************************************************/
SBIT32 BUCKET::ComputeOffsetGoodCase( SBIT32 Displacement,BOOLEAN *Found ) { REGISTER SBIT32 ArrayOffset = (Displacement / AllocationSize);
(*Found) = (Displacement == (ArrayOffset * AllocationSize)); #ifdef DEBUGGING
if ( ArrayOffset >= NumberOfElements ) { Failure( "Array offset in ComputeOffsetGoodCase" ); } #endif
return ArrayOffset; }
/********************************************************************/ /* */ /* Compute the bit vector offset. */ /* */ /* Compute the bit vector offset given the address of the */ /* memory allocation in the page. */ /* */ /********************************************************************/
SBIT32 BUCKET::ComputeOffsetPoorCase( SBIT32 Displacement,BOOLEAN *Found ) { REGISTER SBIT32 ArrayOffset; REGISTER SBIT32 ChunkNumber = (Displacement >> ChunkShift); REGISTER SBIT32 ChunkAddress = (ChunkNumber << ChunkShift); REGISTER SBIT32 ChunkOffset = (Displacement - ChunkAddress); REGISTER SBIT32 AllocationNumber = (ChunkOffset / AllocationSize);
ArrayOffset = ((ChunkNumber * SizeOfChunks) + AllocationNumber);
(*Found) = ( (Displacement) == (ChunkAddress + (AllocationNumber * AllocationSize)) ); #ifdef DEBUGGING
if ( ArrayOffset >= NumberOfElements ) { Failure( "Array offset in ComputeOffsetPoorCase" ); } #endif
return ArrayOffset; }
/********************************************************************/ /* */ /* Compute the bit vector offset. */ /* */ /* Compute the bit vector offset given the address of the */ /* memory allocation in the page. */ /* */ /********************************************************************/
SBIT32 BUCKET::ComputeOffsetWorstCase( SBIT32 Displacement,BOOLEAN *Found ) { REGISTER SBIT32 ArrayOffset; REGISTER SBIT32 ChunkNumber = (Displacement / ChunkSize); REGISTER SBIT32 ChunkAddress = (ChunkNumber * ChunkSize); REGISTER SBIT32 ChunkOffset = (Displacement - ChunkAddress); REGISTER SBIT32 AllocationNumber = (ChunkOffset / AllocationSize);
ArrayOffset = ((ChunkNumber * SizeOfChunks) + AllocationNumber);
(*Found) = ( (Displacement) == (ChunkAddress + (AllocationNumber * AllocationSize)) ); #ifdef DEBUGGING
if ( ArrayOffset >= NumberOfElements ) { Failure( "Array offset in ComputeOffsetWorstCase" ); } #endif
return ArrayOffset; }
/********************************************************************/ /* */ /* Delete a memory allocation. */ /* */ /* We need to delete a single memory allocation from a bucket. */ /* We do this by passing the request on to the page. */ /* */ /********************************************************************/
BOOLEAN BUCKET::Delete( VOID *Address,PAGE *Page,SBIT32 Version ) { AUTO SEARCH_PAGE Details;
//
// When we delete an allocation we need to ensure
// the page has not radically changed since we found
// it. Hence, we compare the current page version
// number with the one we found earlier. If all is
// well we get the details relating to the allocation
// and then delete it.
//
return ( ((Page -> GetVersion()) == Version) && (Page -> FindPage( Address,& Details,False ) != NULL) && (Page -> Delete( & Details )) ); }
/********************************************************************/ /* */ /* Delete a page from the bucket list. */ /* */ /* When a page becomes full it is removed from the bucket list */ /* so it will be no longer inspected when looking for free space. */ /* */ /********************************************************************/
VOID BUCKET::DeleteFromBucketList( PAGE *Page ) { //
// We keep track of the number of active pages on the
// bucket list. This helps us when we need to scan the
// bucket list for some reason later.
//
if ( (-- ActivePages) >= 0 ) { //
// Delete the page from the bucket list as it is
// no longer needed. There are two cases when this
// happens. When the page is full and when the page
// is about to be deleted.
//
Page -> DeleteFromBucketList( & BucketList );
//
// Compute the highest address on the first page. We
// use this information to figure out whether to
// recycle an allocation or pass it along for deletion
// in the cache.
//
Page = (PAGE::FirstInBucketList( & BucketList )); if ( ! Page -> EndOfBucketList() ) { CurrentPage = ( ((VOID*) (((LONG) Page -> GetAddress()) + (PageSize - 1))) ); } else { CurrentPage = ((VOID*) HighestAddress); } } else { Failure( "Active page count in DeleteFromBucketList" ); } }
/********************************************************************/ /* */ /* Insert a page into the bucket list. */ /* */ /* When a page is created or when it changes from being full */ /* to having at least one free slot it is added to the bucket */ /* list so that it can be used to allocate space. */ /* */ /********************************************************************/
VOID BUCKET::InsertInBucketList( PAGE *Page ) { //
// We keep track of the number of active pages on the
// bucket list. This helps us when we need to scan the
// bucket list for some reason later.
//
ActivePages ++;
//
// We insert pages into the list in ascending address
// order. This ensures that we always allocate the
// lowest addresses first. This is done to try to keep
// the working set small and compact.
//
if ( ! BucketList.EndOfList() ) { REGISTER VOID *Address = (Page -> GetAddress()); REGISTER PAGE *Last = (Page -> LastInBucketList( & BucketList ));
//
// We are about to walk the entire page list
// trying to find where to insert this page.
// Lets see if the page needs to be inserted
// at the end of the list. If so have saved
// ourseleves a lot of work and we can exit
// early.
//
if ( Address < (Last -> GetAddress()) ) { REGISTER PAGE *Current;
//
// Well it looks like we need to walk along
// the entire page list to find the correct
// place to insert this element.
//
for ( Current = (Page -> FirstInBucketList( & BucketList )); ! Current -> EndOfBucketList(); Current = Current -> NextInBucketList() ) { //
// While the current address is lower
// than ours we need to keep on walking.
//
if ( Address < (Current -> GetAddress()) ) { //
// We have found the spot so insert
// the bucket just before the current
// bucket.
//
Current -> InsertBeforeInBucketList( & BucketList,Page );
break; } } } else { //
// The page has the highest address so insert
// it at the end of the list.
//
Last -> InsertAfterInBucketList( & BucketList,Page ); } } else { Page -> InsertInBucketList( & BucketList ); }
//
// Compute the highest address on the first page. We can
// use this information to figure out whether to recycle an
// allocation or pass it along for deletion in the cache.
//
Page = (PAGE::FirstInBucketList( & BucketList )); CurrentPage = ( ((VOID*) (((LONG) Page -> GetAddress()) + (PageSize - 1))) ); }
/********************************************************************/ /* */ /* Multiple memory deallocations. */ /* */ /* When the delete cache becomes full we complete any pending */ /* delete requests. We also flush the delete cache when if */ /* we need to allocate additional memory unless recycling is */ /* enabled in which case we just steal it directly from the */ /* delete cache. */ /* */ /********************************************************************/
BOOLEAN BUCKET::MultipleDelete ( ADDRESS_AND_PAGE *Array, SBIT32 *Deleted, SBIT32 Size ) { AUTO SEARCH_PAGE Details; REGISTER SBIT32 Count;
//
// Zero the count of deleted items.
//
(*Deleted) = 0;
//
// Delete each element one at a time. We would love to
// delete them all at once but we haven't got a clue where
// they have come from so we have to do it one at a time.
//
for ( Count=0;Count < Size;Count ++ ) { REGISTER ADDRESS_AND_PAGE *Current = & Array[ Count ]; REGISTER PAGE *Page = Current -> Page;
//
// It may see like a waste of time to batch up all
// the deletions. Why not do them as they arrive.
// There are a number of reasons. The deletes can
// be recycled, batchs of deletes is faster than
// single deletes (due to cache effects) and so on.
//
if ( (Current -> Version == Page -> GetVersion()) && (Page -> FindPage( Current -> Address,& Details,False ) != NULL) && (Page -> Delete( & Details )) ) { (*Deleted) ++; } }
return ((*Deleted) == Size); }
/********************************************************************/ /* */ /* Multiple memory allocations. */ /* */ /* We need to make a multiple memory allocation from this */ /* bucket so walk the bucket list allocating any available */ /* space and return it to the caller. */ /* */ /********************************************************************/
BOOLEAN BUCKET::MultipleNew ( SBIT32 *Actual, VOID *Array[], SBIT32 Requested ) { //
// Zero the count of allocated elements.
//
(*Actual) = 0;
//
// We walk the sorted list of pages with available
// allocations searching for elements to allocate.
//
do { REGISTER PAGE *Page; REGISTER PAGE *NextPage;
//
// Walk the bucket list looking for any available
// free space.
//
for ( Page = (PAGE::FirstInBucketList( & BucketList )); ! Page -> EndOfBucketList(); Page = NextPage ) { REGISTER SBIT32 ActualSize = (Page -> GetPageSize());
//
// Lets find the next page now as the current
// bucket may be removed from the bucket list
// by the following allocation call.
//
NextPage = Page -> NextInBucketList();
//
// We allow the page size to be dynamically
// modified to support a variety of wierd
// data layouts for BBT. If the current page
// is not the standard size then skip it.
//
if ( (ActualSize == NoSize) || (ActualSize == PageSize) ) { //
// We try allocate all the space we need
// from each page in the bucket list. If
// the page has enough space we can exit
// early if not we go round the loop and
// try the next page.
//
if ( Page -> MultipleNew( Actual,Array,Requested ) ) { return True; } } } } while ( NewPage -> CreatePage( (CACHE*) this ) != ((PAGE*) AllocationFailure) );
//
// We see if we managed to allocate all the elements
// we wanted. If so we are happy and we can get out
// of here.
//
if ( (*Actual) < Requested ) { //
// We see if we managed to allocate any elements
// at all. If not we fail the request.
//
if ( (*Actual) > 0 ) { REGISTER SBIT32 Count; REGISTER SBIT32 Delta = ((Requested) - (*Actual));
//
// We are very naughty when we allocate multiple
// elements in that we put them in the array in
// reverse order. The logic is that this is just
// what we want when we allocate out of the cache.
// However, if we are unable to allocate all the
// elements we needed then we have to move the
// pointers down to the base of the array.
//
for ( Count=0;Count < (*Actual);Count ++ ) { Array[ Count ] = Array[ (Count + Delta) ]; } } else { return False; } }
return True; }
/********************************************************************/ /* */ /* Memory allocation. */ /* */ /* We need to make a new memory allocation from this bucket */ /* so search the page list of available space and return a */ /* free element. */ /* */ /********************************************************************/
VOID *BUCKET::New( BOOLEAN SubDivided,SBIT32 NewSize ) { do { REGISTER PAGE *Page;
//
// Walk the bucket list looking for any available
// free space.
//
for ( Page = (PAGE::FirstInBucketList( & BucketList )); ! Page -> EndOfBucketList(); Page = Page -> NextInBucketList() ) { REGISTER SBIT32 ActualSize = (Page -> GetPageSize());
//
// We allow the page size to be dynamically
// modified to support a variety of wierd
// data layouts for BBT. If the current page
// is not the correct size then skip it.
//
if ( (ActualSize == NoSize) || (ActualSize == ((NewSize == NoSize) ? PageSize : NewSize)) ) { //
// We know that any page that appears in
// the bucket list will have at least one
// free element available. So if we find
// that the bucket list a suitable page
// then we know that we can allocate something.
//
return (Page -> New( SubDivided )); } } } while ( NewPage -> CreatePage( ((CACHE*) this),NewSize ) != ((PAGE*) AllocationFailure) );
//
// We were unable to find anything we could allocate
// so fail the request.
//
return ((VOID*) AllocationFailure); }
/********************************************************************/ /* */ /* Release free space. */ /* */ /* We sometimes do not release free space from a bucket as */ /* returning it to the operating system and getting it again */ /* later is very expensive. Here we flush any free space we */ /* have aquired over the user supplied limit. */ /* */ /********************************************************************/
VOID BUCKET::ReleaseSpace( SBIT32 MaxActivePages ) { REGISTER SBIT32 Current = ActivePages;
//
// We only bother to try to trim the number of
// active pages if we are over the limit.
//
if ( Current > MaxActivePages ) { REGISTER PAGE *NextPage; REGISTER PAGE *Page;
//
// Walk the backwards along the bucket list
// and delete the highest addressed free pages
// if we are over the limit.
//
for ( Page = (PAGE::LastInBucketList( & BucketList )); (Current > MaxActivePages) && (! Page -> EndOfBucketList()); Page = NextPage ) { //
// We are walking backwards down the bucket
// list looking for empty pages to delete.
// However, if we find a page we can remove
// it will be automatically removed from the
// list so we need to get the next pointer
// before this happens.
//
NextPage = Page -> PreviousInBucketList();
//
// We can only release a page if it is empty
// if not we must skip it.
//
if ( Page -> Empty() ) { Current --;
DeletePage( Page ); } } } }
/********************************************************************/ /* */ /* Update the bucket information. */ /* */ /* When we create the bucket there is some information that */ /* is not available. Here we update the bucket to make sure */ /* it has all the data we need. */ /* */ /********************************************************************/
VOID BUCKET::UpdateBucket ( FIND *NewFind, HEAP *NewHeap, NEW_PAGE *NewPages, CACHE *NewParentCache ) { REGISTER SBIT16 NewSizeKey = (NewPages -> FindSizeKey( NumberOfElements ));
//
// We compute and verify the size key to make sure
// it is suitable for all the pages that we will
// create after the heap constructor is completed.
//
if ( NewSizeKey != NoSizeKey ) { //
// Update the size key and the connections.
//
SizeKey = NewSizeKey;
UpdateConnections ( NewFind, NewHeap, NewPages, NewParentCache ); } else { Failure( "Bucket can't get a size key in UpdateBucket" ); } }
/********************************************************************/ /* */ /* Class destructor. */ /* */ /* Destory the allocation bucket. */ /* */ /********************************************************************/
BUCKET::~BUCKET( VOID ) { /* void */ }
|