// Ruler // 1 2 3 4 5 6 7 8 //345678901234567890123456789012345678901234567890123456789012345678901234567890 /********************************************************************/ /* */ /* The standard layout. */ /* */ /* The standard layout for 'cpp' files in 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 this is not required is simply omitted. */ /* */ /********************************************************************/ #include "HeapPCH.hpp" #include "Cache.hpp" #include "Heap.hpp" /********************************************************************/ /* */ /* Constants local to the class. */ /* */ /* The constants supplied here control the maximum size of */ /* the cache. */ /* */ /********************************************************************/ CONST SBIT32 MaxCacheSize = ((2 << 16)-1); /********************************************************************/ /* */ /* Class constructor. */ /* */ /* Create a new allocation cache and prepare it for use. A */ /* is inactive until the first request is received at which */ /* time it springs into life. */ /* */ /********************************************************************/ CACHE::CACHE ( SBIT32 NewAllocationSize, SBIT32 NewCacheSize, SBIT32 NewChunkSize, SBIT32 NewPageSize, BOOLEAN NewStealing, BOOLEAN NewThreadSafe ) : // // Call the constructors for the contained classes. // BUCKET( NewAllocationSize,NewChunkSize,NewPageSize ) { // // We need to be very careful with the configuration // information as it has come indirectly from the // user and my be bogus. // if ( (NewCacheSize >= 0) && (NewCacheSize < MaxCacheSize) ) { // // Setup the cache and mark it as inactive. // Active = False; Stealing = NewStealing; ThreadSafe = NewThreadSafe; #ifdef ENABLE_HEAP_STATISTICS CacheFills = 0; CacheFlushes = 0; HighTide = 0; HighWater = 0; InUse = 0; #endif CacheSize = ((SBIT16) NewCacheSize); FillSize = 1; NumberOfChildren = 0; // // The stacks that may later contain allocations // are set to zero just to be neat. // DeleteStack = NULL; NewStack = NULL; TopOfDeleteStack = 0; TopOfNewStack = 0; } else { Failure( "Cache size in constructor for CACHE" ); } } /********************************************************************/ /* */ /* Create the cache stacks. */ /* */ /* A cache is created on demand. We do this when we get the */ /* first allocation or deallocation request. */ /* */ /********************************************************************/ VOID CACHE::CreateCacheStacks( VOID ) { // // We allocate the cache stacks from the internal // new page allocator if we have not done it already. // if ( DeleteStack == NULL ) { REGISTER SBIT32 Size = (CacheSize * sizeof(ADDRESS_AND_PAGE)); DeleteStack = ((ADDRESS_AND_PAGE*) (NewPage -> NewCacheStack( Size ))); } if ( NewStack == NULL ) { REGISTER SBIT32 Size = (CacheSize * sizeof(VOID*)); NewStack = ((VOID**) (NewPage -> NewCacheStack( Size ))); } // // We can now activate the cache as long as we // were able to allocate both stacks. // if ( (NewStack != NULL ) && (DeleteStack != NULL ) ) { // // We have completed creating the cache so set // various flags and zero various counters. // Active = True; // // Setup the fill size. // FillSize = 1; // // Zero the stack tops. // TopOfDeleteStack = 0; TopOfNewStack = 0; } } /********************************************************************/ /* */ /* Create a new data page. */ /* */ /* When we create a new page we also need to allocate some */ /* memory to hold the associated data. */ /* */ /********************************************************************/ VOID *CACHE::CreateDataPage( VOID ) { REGISTER VOID *NewMemory; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // Create a data page. // NewMemory = ((BUCKET*) this) -> New( True ); // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); return NewMemory; } #ifdef ENABLE_HEAP_STATISTICS /********************************************************************/ /* */ /* Compute high water. */ /* */ /* Compute the high water mark for the current cache. */ /* */ /********************************************************************/ VOID CACHE::ComputeHighWater( SBIT32 Size ) { // // Update the usage statistics. // if ( (InUse += Size) > HighTide ) { HighTide = InUse; if ( HighTide > HighWater ) { HighWater = HighTide; } } } #endif /********************************************************************/ /* */ /* A memory deallocation cache. */ /* */ /* We cache memory deallocation requests to improve performance. */ /* We do this by stacking requests until we have a batch. */ /* */ /********************************************************************/ BOOLEAN CACHE::Delete( VOID *Address,PAGE *Page,SBIT32 Version ) { REGISTER BOOLEAN Result; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // At various times the cache may be either disabled // or inactive. Here we ensure that we are able to use // the cache. If not we bypass it and call the bucket // directly. // if ( Active ) { // // If recycling is allowed and the address is // on the current page or a previous page and // there is space on the new stack then put the // element in the new stack for immediate reuse. // if ( (Stealing) && (Address < GetCurrentPage()) && (TopOfNewStack < CacheSize) ) { // // The address is suitable for immediate // reuse. So put it on the stack of new // elements. // NewStack[ (TopOfNewStack ++) ] = Address; Result = True; } else { REGISTER ADDRESS_AND_PAGE *Current = (& DeleteStack[ TopOfDeleteStack ++ ]); // // The address would best be deleted before // being reused. // Current -> Address = Address; Current -> Page = Page; Current -> Version = Version; // // When the delete stack is full we flush it. // if ( TopOfDeleteStack >= CacheSize ) { AUTO SBIT32 Deleted; // // Flush the delete stack. // Result = ( ((BUCKET*) this) -> MultipleDelete ( DeleteStack, & Deleted, TopOfDeleteStack ) ); #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. There // is a nasty case here where we cache // a delete only to find out later that // it was bogus. When this occurs we // have to increase the 'InUse' count // to allow for this situation. // CacheFlushes ++; InUse += (TopOfDeleteStack - Deleted); #endif // // Zero the top of the stack. // TopOfDeleteStack = 0; } else { Result = True; } } } else { // // Delete the element. // Result = (((BUCKET*) this) -> Delete( Address,Page,Version )); } #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. // if ( Result ) { InUse --; } #endif // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); return Result; } /********************************************************************/ /* */ /* Delete all allocations. */ /* */ /* The entire heap is about to be deleted under our feet. We */ /* need to prepare for this by disabling the cache as its */ /* contents will disappear as well. */ /* */ /********************************************************************/ VOID CACHE::DeleteAll( VOID ) { // // Disable the cache if needed. // Active = False; #ifdef ENABLE_HEAP_STATISTICS // // Zero the statistics. // HighTide = 0; InUse = 0; #endif // // Setup the fill size. // FillSize = 1; // // Zero the top of stacks. // TopOfDeleteStack = 0; TopOfNewStack = 0; } /********************************************************************/ /* */ /* Delete a data page. */ /* */ /* Delete a data page that was associated with a smaller cache */ /* so its space can be reused. */ /* */ /********************************************************************/ BOOLEAN CACHE::DeleteDataPage( VOID *Address ) { AUTO SEARCH_PAGE Details; REGISTER BOOLEAN Result; REGISTER PAGE *Page; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // Find the description of the data page we need to // delete and make sure it is valid. // Find -> ClaimFindShareLock(); Page = FindParentPage( Address ); if ( Page != NULL ) { Page = (Page -> FindPage( Address,& Details,False )); } Find -> ReleaseFindShareLock(); // // Delete the data page. // if ( Page != NULL ) { Result = (Page -> Delete( & Details )); } else { Failure( "No data page in DeleteDataPage" ); } // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); return Result; } /********************************************************************/ /* */ /* Multiple memory allocations. */ /* */ /* The allocation cache contains preallocated memory from the */ /* associated allocation bucket. The cache will supply these */ /* preallocated elements with the minimum fuss to any caller. */ /* */ /********************************************************************/ BOOLEAN CACHE::MultipleNew( SBIT32 *Actual,VOID *Array[],SBIT32 Requested ) { REGISTER BOOLEAN Result; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // At various times the cache may be either disabled // or inactive. Here we ensure that we are able to use // the cache. If not we bypass it and call the bucket // directly. // if ( Active ) { // // We have been asked to allocalte multiple // new elements. If it appears that we don't // have enough elements available but stealing // is allowed we can try raiding the deleted // stack. // if ( (Requested > TopOfNewStack) && (Stealing) ) { while ( (TopOfDeleteStack > 0) && (TopOfNewStack < CacheSize) ) { NewStack[ (TopOfNewStack ++) ] = (DeleteStack[ (-- TopOfDeleteStack) ].Address); } } // // We will allocate from the cache if requested // size is smaller than the number of available // elements. // if ( Requested <= TopOfNewStack ) { REGISTER SBIT32 Count; // // We need to copy the elements out of the // cache into the callers array. // for ( Count=0;Count < Requested;Count ++ ) { Array[ Count ] = NewStack[ (-- TopOfNewStack) ]; } (*Actual) = Requested; Result = True; } else { REGISTER BUCKET *Bucket = ((BUCKET*) this); // // We don't have enough elements in the cache // so we allocate directly from the bucket. // Result = ( Bucket -> MultipleNew ( Actual, Array, Requested ) ); // // We fill up the cache so we have a good // chance of dealing with any following // requests if it is less than half full. // if ( TopOfNewStack <= (CacheSize / 2) ) { AUTO SBIT32 NewSize; REGISTER SBIT32 MaxSize = (CacheSize - TopOfNewStack); // // We slowly increse the fill size // of the cache to make sure we don't // waste too much space. // if ( FillSize < CacheSize ) { if ( (FillSize *= 2) > CacheSize ) { FillSize = CacheSize; } } // // Bulk load the cache with new // elements. // Bucket -> MultipleNew ( & NewSize, & NewStack[ TopOfNewStack ], ((FillSize < MaxSize) ? FillSize : MaxSize) ); #ifdef ENABLE_HEAP_STATISTICS CacheFills ++; #endif TopOfNewStack += NewSize; } } } else { // // We may want to enable the cache for next // time so see if this needs to be done. // if ( CacheSize > 1 ) { CreateCacheStacks(); } // // The cache is disabled so go directly to the // bucket. // Result = ((BUCKET*) this) -> MultipleNew ( Actual, Array, Requested ); } #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. // ComputeHighWater( (*Actual) ); #endif // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); return Result; } /********************************************************************/ /* */ /* Memory allocation. */ /* */ /* The allocation cache contains preallocated memory from the */ /* associated allocation bucket. The cache will supply these */ /* preallocated elements with the minimum fuss to any caller. */ /* */ /********************************************************************/ VOID *CACHE::New( VOID ) { REGISTER VOID *NewMemory; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // At various times the cache may be either disabled // or inactive. Here we ensure that we are able to use // the cache. If not we bypass it and call the bucket // directly. // if ( Active ) { // // We first try the stack for new allocations // to see if there are any available elements. // if ( TopOfNewStack > 0 ) { NewMemory = (NewStack[ (-- TopOfNewStack) ]); } else { // // When stealing is allowed we will recycle // elements from the top of the deleted stack. // if ( (TopOfDeleteStack > 0) && (Stealing) ) { NewMemory = (DeleteStack[ (-- TopOfDeleteStack) ].Address); } else { // // We slowly increse the fill size // of the cache to make sure we don't // waste too much space. // if ( FillSize < CacheSize ) { if ( (FillSize *= 2) > CacheSize ) { FillSize = CacheSize; } } // // We need to bulk load some new // memory from the heap. // if ( ((BUCKET*) this) -> MultipleNew ( & TopOfNewStack, NewStack, FillSize ) ) { // // Update the statistics and return // the top element on the stack. // #ifdef ENABLE_HEAP_STATISTICS CacheFills ++; #endif NewMemory = NewStack[ (-- TopOfNewStack) ]; } else { // // Update the statistics and fail // the request for memeory. // NewMemory = ((VOID*) AllocationFailure); } } } } else { // // We may want to enable the cache for next // time so see if this needs to be done. // if ( CacheSize > 1 ) { CreateCacheStacks(); } // // The cache is disabled so go directly to the // bucket. // NewMemory = ((BUCKET*) this) -> New( False ); } #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. // ComputeHighWater( (NewMemory != ((VOID*) AllocationFailure)) ); #endif // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); // // Prefetch the first cache line if we are running // a Pentium III or better. // Prefetch.L1( ((CHAR*) NewMemory),1 ); return NewMemory; } /********************************************************************/ /* */ /* Memory allocation for non-standard sizes. */ /* */ /* A non standard sized allocation simply by-passes the cache */ /* but it still needs to hold the lock to prevent failure on */ /* SMP systems. */ /* */ /********************************************************************/ VOID *CACHE::New( BOOLEAN SubDivided,SBIT32 NewSize ) { REGISTER VOID *NewMemory; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // Allocate a non-standard sized block. // NewMemory = ((BUCKET*) this) -> New( SubDivided,NewSize ); #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. // ComputeHighWater( (NewMemory != ((VOID*) AllocationFailure)) ); #endif // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); return NewMemory; } /********************************************************************/ /* */ /* 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 CACHE::ReleaseSpace( SBIT32 MaxActivePages ) { // // When there is a potential for multiple threads // we claim the cache lock. // ClaimCacheLock(); // // Release the free space from the backet. // ((BUCKET*) this) -> ReleaseSpace( MaxActivePages ); // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); } /********************************************************************/ /* */ /* Search the cacahe for an allocation. */ /* */ /* We sometimes need to search the cache to see if an */ /* allocation is currently in the cacahe awaiting allocation */ /* or release. */ /* */ /********************************************************************/ BOOLEAN CACHE::SearchCache( VOID *Address ) { REGISTER BOOLEAN Result = False; // // We check to see if the cache is active. // if ( Active ) { // // When there is a potential for multiple // threads we claim the cache lock. // ClaimCacheLock(); // // We check to see if the cache is still // active. // if ( Active ) { REGISTER SBIT32 Count; // // Search the allocated cache. // for ( Count=(TopOfNewStack-1);Count >= 0;Count -- ) { if ( Address == NewStack[ Count ] ) { Result = True; break; } } // // If it has not been found yet then try // the deleted cache. // if ( ! Result ) { // // Search the deleted cache. // for ( Count=(TopOfDeleteStack-1);Count >= 0;Count -- ) { if ( Address == DeleteStack[ Count ].Address ) { Result = True; break; } } } } // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); } return Result; } /********************************************************************/ /* */ /* Truncate the heap. */ /* */ /* Flush the cache to release the maximum amount of space back */ /* to the operating system. This is slow but may be very */ /* valuable in some situations. */ /* */ /********************************************************************/ BOOLEAN CACHE::Truncate( VOID ) { REGISTER BOOLEAN Result = True; // // When there is a potential for multiple threads we // claim the cache lock. // ClaimCacheLock(); // // Disable the cache if needed. // Active = False; // // Setup the fill size. // FillSize = 1; // // Flush any elements in the delete cache. // We do this now because we need to use // the delete cache below. // if ( TopOfDeleteStack > 0 ) { AUTO SBIT32 Deleted; // // Flush the delete stack. // Result = ( ((BUCKET*) this) -> MultipleDelete ( DeleteStack, & Deleted, TopOfDeleteStack ) && (Result) ); #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. There // is a nasty case here where we cache // a delete only to find out later that // it was bogus. When this occurs we // have to increase the 'InUse' count // to allow for this situation. // CacheFlushes ++; InUse += (TopOfDeleteStack - Deleted); #endif // // Zero the top of the stack. // TopOfDeleteStack = 0; } // // Flush any elements in the new cache by // copying them over to the delete cache // and adding the additional information // required. // if ( TopOfNewStack > 0 ) { // // We need to find the data page for each // allocation we have in the new cache. // Claim the lock here to make things a // little more efficient. // Find -> ClaimFindShareLock(); // // We copy each allocation across and // add the associated page information. // for ( TopOfNewStack --;TopOfNewStack >= 0;TopOfNewStack -- ) { REGISTER VOID *Address = (NewStack[ TopOfNewStack ]); REGISTER PAGE *Page = (ParentCache -> FindChildPage( Address )); // // You would think that any memory in the // new cache had to be valid. Well it // does except in the case when we have // 'Recycle' set and somebody does a double // delete on a valid heap address. // if ( Page != NULL ) { REGISTER ADDRESS_AND_PAGE *Current = (& DeleteStack[ TopOfDeleteStack ++ ]); // // We need to find the allocation page // where the memory was allocated from // so we can delete it. // Current -> Address = Address; Current -> Page = Page; Current -> Version = Page -> GetVersion(); } else { #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. There // is a nasty case here where we cache // a delete only to find out later that // it was bogus. When this occurs we // have to increase the 'InUse' count // to allow for this situation. // InUse ++; #endif Result = False; } } // // Release the lock. // Find -> ReleaseFindShareLock(); } // // Flush the delete cache again to delete // any new elements that we added to it // above. // if ( TopOfDeleteStack > 0 ) { AUTO SBIT32 Deleted; // // Flush the delete stack. // Result = ( ((BUCKET*) this) -> MultipleDelete ( DeleteStack, & Deleted, TopOfDeleteStack ) && (Result) ); #ifdef ENABLE_HEAP_STATISTICS // // Update the usage statistics. There // is a nasty case here where we cache // a delete only to find out later that // it was bogus. When this occurs we // have to increase the 'InUse' count // to allow for this situation. // CacheFlushes ++; InUse += (TopOfDeleteStack - Deleted); #endif // // Zero the top of the stack. // TopOfDeleteStack = 0; } // // Release any lock we may have claimed earlier. // ReleaseCacheLock(); return Result; } /********************************************************************/ /* */ /* 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 it needs. */ /* */ /********************************************************************/ VOID CACHE::UpdateCache ( FIND *NewFind, HEAP *NewHeap, NEW_PAGE *NewPages, CACHE *NewParentCache ) { // // Notify the parent cache that it has a new // child. // if ( NewParentCache != ((CACHE*) GlobalRoot) ) { NewParentCache -> NumberOfChildren ++; } // // Update the allocation bucket. // UpdateBucket ( NewFind, NewHeap, NewPages, NewParentCache ); } /********************************************************************/ /* */ /* Class destructor. */ /* */ /* Destory the cache and ensure it is disabled. */ /* */ /********************************************************************/ CACHE::~CACHE( VOID ) { if ( Active ) { Failure( "Cache active in destructor for CACHE" ); } }