|
|
#ifndef _RESMGR_HXX_INCLUDED
#define _RESMGR_HXX_INCLUDED
// asserts
//
// #define RESMGRAssert to point to your favorite assert function per #include
#ifdef RESMGRAssert
#else // !RESMGRAssert
#define RESMGRAssert Assert
#endif // RESMGRAssert
#ifdef COLLAssert
#else // !COLLAssert
#define COLLAssert RESMGRAssert
#endif // COLLAssert
#include "collection.hxx"
#include <math.h>
namespace RESMGR {
//////////////////////////////////////////////////////////////////////////////////////////
// CDBAResourceAllocationManager
//
// Implements a class used to manage how much memory a resource pool should use as a
// function of the real-time usage characteristics of that pool and the entire system.
// Naturally, the managed resource would have to be of a type where retention in memory
// is optional, at least to some extent.
//
// The resource pool's size is managed by the Dynamic Buffer Allocation (DBA) algorithm.
// DBA was originally invented by Andrew E. Goodsell in 1997 to manage the size of the
// multiple database page caches in Exchange Server 5.5. DBA is not patented and is a
// trade secret of Microsoft Corporation.
//
// The class is written to be platform independent. As such, there are several pure
// virtual functions that must be implemented to provide the data about the OS that the
// resource manager needs to make decisions. To provide these functions, derive the
// class for the desired platform and then define these functions:
//
// TotalPhysicalMemoryPages()
//
// Returns the total number of pageable physical memory pages in the system.
//
// AvailablePhysicalMemoryPages()
//
// Returns the number of available pageable physical memory pages in the system.
//
// PhysicalMemoryPageSize()
//
// Returns the size of a single physical memory page.
//
// TotalPhysicalMemoryPageEvictions()
//
// Returns the total number of physical page evictions that have occurred.
//
// There are also several pure virtual functions that must be implemented to provide
// resource specific data. To provide these functions, derive the class for each
// resource to manage and then define these functions:
//
// TotalResources()
//
// Returns the total number of resources in the resource pool.
//
// ResourceSize()
//
// Returns the size of a single resource.
//
// TotalResourceEvictions()
//
// Returns the total number of resource evictions that have occurred.
//
// There is one final pure virtual function that must be implemented to provide the
// resource pool with the size recommendation issued by the resource manager:
//
// SetOptimalResourcePoolSize( size_t cResource )
//
// Delivers the recommendation of the resource manager for the total number of
// resources that should be in the resource pool at this time. Note that this
// size is merely a suggestion that can be followed to maximize overall system
// performance. It is not mandatory to follow this suggestion exactly or
// instantaneously.
//
// This recommendation can be queried at any time via OptimalResourcePoolSize().
//
// Finally, UpdateStatistics() must be called once per second to sample this data and
// and to update the current resource pool size recommendation. The statistics need to
// be reset before use via ResetStatistics(). This function can be called at any time
// to reset the manager's statistics for whatever reason.
class CDBAResourceAllocationManager { public:
CDBAResourceAllocationManager(); virtual ~CDBAResourceAllocationManager();
virtual void ResetStatistics(); virtual void UpdateStatistics(); virtual size_t OptimalResourcePoolSize();
protected:
virtual size_t TotalPhysicalMemoryPages() = 0; virtual size_t AvailablePhysicalMemoryPages() = 0; virtual size_t PhysicalMemoryPageSize() = 0; virtual long TotalPhysicalMemoryPageEvictions() = 0;
virtual size_t TotalResources() = 0; virtual size_t ResourceSize() = 0; virtual long TotalResourceEvictions() = 0;
virtual void SetOptimalResourcePoolSize( size_t cResource ) = 0;
private:
enum { m_cRollingAvgDepth = 3 };
size_t m_cTotalPage[ m_cRollingAvgDepth ]; int m_icTotalPageOldest; double m_cTotalPageSum; double m_cTotalPageAvg;
size_t m_cAvailPage[ m_cRollingAvgDepth ]; int m_icAvailPageOldest; double m_cAvailPageSum; double m_cAvailPageAvg;
size_t m_cbPage[ m_cRollingAvgDepth ]; int m_icbPageOldest; double m_cbPageSum; double m_cbPageAvg; long m_cPageEvict2; long m_cPageEvict1; long m_dcPageEvict[ m_cRollingAvgDepth ]; int m_idcPageEvictOldest; double m_dcPageEvictSum; double m_dcPageEvictAvg; size_t m_cbResource[ m_cRollingAvgDepth ]; int m_icbResourceOldest; double m_cbResourceSum; double m_cbResourceAvg; size_t m_cTotalResource[ m_cRollingAvgDepth ]; int m_icTotalResourceOldest; double m_cTotalResourceSum; double m_cTotalResourceAvg; long m_cResourceEvict2; long m_cResourceEvict1; long m_dcResourceEvict[ m_cRollingAvgDepth ]; int m_idcResourceEvictOldest; double m_dcResourceEvictSum; double m_dcResourceEvictAvg;
size_t m_cResourceMin; size_t m_cResourceMax; };
// ctor
inline CDBAResourceAllocationManager::CDBAResourceAllocationManager() { // nothing to do
}
// virtual dtor
inline CDBAResourceAllocationManager::~CDBAResourceAllocationManager() { // nothing to do
}
// resets the observed statistics for the resource pool and the system such
// that the current state appears to have been the steady state for as long as
// past behavioral data is retained
inline void CDBAResourceAllocationManager::ResetStatistics() { // load all statistics
size_t cTotalPageInit = TotalPhysicalMemoryPages(); for ( m_icTotalPageOldest = m_cRollingAvgDepth - 1; m_icTotalPageOldest >= 0; m_icTotalPageOldest-- ) { m_cTotalPage[ m_icTotalPageOldest ] = cTotalPageInit; } m_icTotalPageOldest = 0; m_cTotalPageSum = m_cRollingAvgDepth * (double)cTotalPageInit; m_cTotalPageAvg = (double)cTotalPageInit;
size_t cAvailPageInit = AvailablePhysicalMemoryPages(); for ( m_icAvailPageOldest = m_cRollingAvgDepth - 1; m_icAvailPageOldest >= 0; m_icAvailPageOldest-- ) { m_cAvailPage[ m_icAvailPageOldest ] = cAvailPageInit; } m_icAvailPageOldest = 0; m_cAvailPageSum = m_cRollingAvgDepth * (double)cAvailPageInit; m_cAvailPageAvg = (double)cAvailPageInit;
size_t cbPageInit = PhysicalMemoryPageSize(); for ( m_icbPageOldest = m_cRollingAvgDepth - 1; m_icbPageOldest >= 0; m_icbPageOldest-- ) { m_cbPage[ m_icbPageOldest ] = cbPageInit; } m_icbPageOldest = 0; m_cbPageSum = m_cRollingAvgDepth * (double)cbPageInit; m_cbPageAvg = (double)cbPageInit;
long cPageEvictInit = TotalPhysicalMemoryPageEvictions(); m_cPageEvict2 = cPageEvictInit; m_cPageEvict1 = cPageEvictInit; for ( m_idcPageEvictOldest = m_cRollingAvgDepth - 1; m_idcPageEvictOldest >= 0; m_idcPageEvictOldest-- ) { m_dcPageEvict[ m_idcPageEvictOldest ] = 0; } m_idcPageEvictOldest = 0; m_dcPageEvictSum = 0; m_dcPageEvictAvg = 0;
size_t cbResourceInit = ResourceSize(); for ( m_icbResourceOldest = m_cRollingAvgDepth - 1; m_icbResourceOldest >= 0; m_icbResourceOldest-- ) { m_cbResource[ m_icbResourceOldest ] = cbResourceInit; } m_icbResourceOldest = 0; m_cbResourceSum = m_cRollingAvgDepth * (double)cbResourceInit; m_cbResourceAvg = (double)cbResourceInit;
size_t cTotalResourceInit = TotalResources(); for ( m_icTotalResourceOldest = m_cRollingAvgDepth - 1; m_icTotalResourceOldest >= 0; m_icTotalResourceOldest-- ) { m_cTotalResource[ m_icTotalResourceOldest ] = cTotalResourceInit; } m_icTotalResourceOldest = 0; m_cTotalResourceSum = m_cRollingAvgDepth * (double)cTotalResourceInit; m_cTotalResourceAvg = (double)cTotalResourceInit;
long cResourceEvictInit = TotalResourceEvictions(); m_cResourceEvict2 = cResourceEvictInit; m_cResourceEvict1 = cResourceEvictInit; for ( m_idcResourceEvictOldest = m_cRollingAvgDepth - 1; m_idcResourceEvictOldest >= 0; m_idcResourceEvictOldest-- ) { m_dcResourceEvict[ m_idcResourceEvictOldest ] = 0; } m_idcResourceEvictOldest = 0; m_dcResourceEvictSum = 0; m_dcResourceEvictAvg = 0;
m_cResourceMin = 1; m_cResourceMax = size_t( m_cTotalPageAvg * m_cbPageAvg / m_cbResourceAvg ); }
// updates the observed characteristics of the resource pool and the system as
// a whole for the purpose of making real-time suggestions as to the size of
// the resource pool. this function should be called at approximately 1 Hz
inline void CDBAResourceAllocationManager::UpdateStatistics() { // update all statistics
m_cTotalPageSum -= m_cTotalPage[ m_icTotalPageOldest ]; m_cTotalPage[ m_icTotalPageOldest ] = TotalPhysicalMemoryPages(); m_cTotalPageSum += m_cTotalPage[ m_icTotalPageOldest ]; m_icTotalPageOldest = ( m_icTotalPageOldest + 1 ) % m_cRollingAvgDepth; m_cTotalPageAvg = m_cTotalPageSum / m_cRollingAvgDepth;
m_cAvailPageSum -= m_cAvailPage[ m_icAvailPageOldest ]; m_cAvailPage[ m_icAvailPageOldest ] = AvailablePhysicalMemoryPages(); m_cAvailPageSum += m_cAvailPage[ m_icAvailPageOldest ]; m_icAvailPageOldest = ( m_icAvailPageOldest + 1 ) % m_cRollingAvgDepth; m_cAvailPageAvg = m_cAvailPageSum / m_cRollingAvgDepth;
m_cbPageSum -= m_cbPage[ m_icbPageOldest ]; m_cbPage[ m_icbPageOldest ] = PhysicalMemoryPageSize(); m_cbPageSum += m_cbPage[ m_icbPageOldest ]; m_icbPageOldest = ( m_icbPageOldest + 1 ) % m_cRollingAvgDepth; m_cbPageAvg = m_cbPageSum / m_cRollingAvgDepth;
m_cPageEvict2 = m_cPageEvict1; m_cPageEvict1 = TotalPhysicalMemoryPageEvictions(); m_dcPageEvictSum -= m_dcPageEvict[ m_idcPageEvictOldest ]; m_dcPageEvict[ m_idcPageEvictOldest ] = m_cPageEvict1 - m_cPageEvict2; m_dcPageEvictSum += m_dcPageEvict[ m_idcPageEvictOldest ]; m_idcPageEvictOldest = ( m_idcPageEvictOldest + 1 ) % m_cRollingAvgDepth; m_dcPageEvictAvg = m_dcPageEvictSum / m_cRollingAvgDepth;
m_cbResourceSum -= m_cbResource[ m_icbResourceOldest ]; m_cbResource[ m_icbResourceOldest ] = ResourceSize(); m_cbResourceSum += m_cbResource[ m_icbResourceOldest ]; m_icbResourceOldest = ( m_icbResourceOldest + 1 ) % m_cRollingAvgDepth; m_cbResourceAvg = m_cbResourceSum / m_cRollingAvgDepth;
m_cTotalResourceSum -= m_cTotalResource[ m_icTotalResourceOldest ]; m_cTotalResource[ m_icTotalResourceOldest ] = TotalResources(); m_cTotalResourceSum += m_cTotalResource[ m_icTotalResourceOldest ]; m_icTotalResourceOldest = ( m_icTotalResourceOldest + 1 ) % m_cRollingAvgDepth; m_cTotalResourceAvg = m_cTotalResourceSum / m_cRollingAvgDepth;
m_cResourceEvict2 = m_cResourceEvict1; m_cResourceEvict1 = TotalResourceEvictions(); m_dcResourceEvictSum -= m_dcResourceEvict[ m_idcResourceEvictOldest ]; m_dcResourceEvict[ m_idcResourceEvictOldest ] = m_cResourceEvict1 - m_cResourceEvict2; m_dcResourceEvictSum += m_dcResourceEvict[ m_idcResourceEvictOldest ]; m_idcResourceEvictOldest = ( m_idcResourceEvictOldest + 1 ) % m_cRollingAvgDepth; m_dcResourceEvictAvg = m_dcResourceEvictSum / m_cRollingAvgDepth;
m_cResourceMin = 1; m_cResourceMax = size_t( m_cTotalPageAvg * m_cbPageAvg / m_cbResourceAvg );
// recommend a size for the resource pool
SetOptimalResourcePoolSize( OptimalResourcePoolSize() ); }
// returns the current recommended size for the resource pool according to the
// observed characteristics of that pool and the system as a whole as sampled
// via UpdateStatistics(). the recommended size is given in bytes. note that
// the recommended size is merely a suggestion that can be used to maximize
// overall system performance. it is not mandatory to follow this suggestion
// exactly or instantaneously
inline size_t CDBAResourceAllocationManager::OptimalResourcePoolSize() { // our goal is to equalize the internal memory pressure in our resource
// pool with the external memory pressure from the rest of the system. we
// do this using the following differential equation:
//
// dRM/dt = k1 * AM/TM * dRE/dt - k2 * RM/TM * dPE/dt
//
// RM = Total Resource Memory
// k1 = fudge factor 1 (1.0 by default)
// AM = Available System Memory
// TM = Total System Memory
// RE = Resource Evictions
// k2 = fudge factor 2 (1.0 by default)
// PE = System Page Evictions
//
// this equation has two parts:
//
// the first half tries to grow the resource pool proportional to the
// internal resource pool memory pressure and to the amount of memory
// available in the system
//
// the second half tries to shrink the resource pool proportional to the
// external resource pool memory pressure and to the amount of resource
// memory we own
//
// in other words, the more available memory and the faster resources are
// evicted, the faster the pool grows. the larger the pool size and the
// faster memory pages are evicted, the faster the pool shrinks
//
// the method behind the madness is an approximation of page victimization
// by the OS under memory pressure. the more memory we have then the more
// likely it is that one of our pages will be evicted. to pre-emptively
// avoid this, we'll voluntarily reduce our memory usage when we determine
// that memory pressure will cause our pages to get evicted. note that
// the reverse of this is also true. if we make it clear to the OS that
// we need this memory by slowly growing while under memory pressure, we
// force pages of memory owned by others to be victimized to be used by
// our pool. the more memory any one of those others owns, the more
// likely it is that one of their pages will be stolen. if any of the
// others is another resource allocation manager then they can communicate
// their memory needs indirectly via the memory pressure they each apply
// on the system. the net result is an equilibrium where each pool gets
// the memory it "deserves"
// compute the change in the amount of memory that this resource pool
// should have
double k1 = 1.0; double cbAvailMemory = m_cAvailPageAvg * m_cbPageAvg; double dcbResourceEvict = m_dcResourceEvictAvg * m_cbResourceAvg; double k2 = 1.0; double cbResourcePool = m_cTotalResourceAvg * m_cbResourceAvg; double dcbPageEvict = m_dcPageEvictAvg * m_cbPageAvg; double cbTotalMemory = m_cTotalPageAvg * m_cbPageAvg;
double dcTotalResource = ( k1 * cbAvailMemory * dcbResourceEvict - k2 * cbResourcePool * dcbPageEvict ) / ( cbTotalMemory * m_cbResourceAvg );
// round the delta away from zero so that we don't get stuck at the same
// number of resources if the delta is commonly less than one resource
dcTotalResource += dcTotalResource < 0 ? -0.5 : 0.5; modf( dcTotalResource, &dcTotalResource );
// get the new amount of memory that this resource pool should have
double cResourceNew = TotalResources() + dcTotalResource;
// limit the new resource pool size to the realm of sanity just in case...
cResourceNew = max( cResourceNew, m_cResourceMin ); cResourceNew = min( cResourceNew, m_cResourceMax );
// return the suggested resource pool size
return size_t( cResourceNew ); }
//////////////////////////////////////////////////////////////////////////////////////////
// CLRUKResourceUtilityManager
//
// Implements a class used to manage a resource via the LRUK Replacement Policy. Each
// resource can be cached, touched, and evicted. The current pool of resources can
// also be scanned in order by ascending utility to allow eviction of less useful
// resouces by a clean procedure.
//
// m_Kmax = the maximum K-ness of the LRUK Replacement Policy (the actual
// K-ness can be set at init time)
// CResource = class representing the managed resource
// OffsetOfIC = inline function returning the offset of the CInvasiveContext
// contained in each CResource
// CKey = class representing the key which uniquely identifies each
// instance of a CResource. CKey must implement a default ctor,
// operator=(), and operator==()
//
// You must use the DECLARE_LRUK_RESOURCE_UTILITY_MANAGER macro to declare this class.
//
// You must implement the following inline function and pass its hashing characteristics
// into ErrInit():
//
// int CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::CHistoryTable::CKeyEntry::Hash( const CKey& key );
// CONSIDER: add code to special case LRU
// CONSIDER: add a fast allocator for CHistorys
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > class CLRUKResourceUtilityManager { public:
// timestamp used for resource touch times
typedef DWORD TICK;
enum { tickNil = ~TICK( 0 ) }; enum { tickMask = ~TICK( 0 ) ^ TICK( 1 ) }; // class containing context needed per CResource
class CInvasiveContext { public:
CInvasiveContext() {} ~CInvasiveContext() {}
static SIZE_T OffsetOfILE() { return OffsetOfIC() + OffsetOf( CInvasiveContext, m_aiic ); } static SIZE_T OffsetOfAIIC() { return OffsetOfIC() + OffsetOf( CInvasiveContext, m_aiic ); }
private:
friend class CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >;
CApproximateIndex< TICK, CResource, OffsetOfAIIC >::CInvasiveContext m_aiic; TICK m_tickLast; TICK m_rgtick[ m_Kmax ]; TICK m_tickIndex; };
// API Error Codes
enum ERR { errSuccess, errInvalidParameter, errOutOfMemory, errResourceNotCached, errNoCurrentResource, };
// API Lock Context
class CLock;
public:
// ctor / dtor
CLRUKResourceUtilityManager( const int Rank ); ~CLRUKResourceUtilityManager();
// API
ERR ErrInit( const int K, const double csecCorrelatedTouch, const double csecTimeout, const double csecUncertainty, const double dblHashLoadFactor, const double dblHashUniformity, const double dblSpeedSizeTradeoff ); void Term();
ERR ErrCacheResource( const CKey& key, CResource* const pres, const BOOL fUseHistory = fTrue ); void TouchResource( CResource* const pres, const TICK tickNow = _TickCurrentTime() ); BOOL FHotResource( CResource* const pres ); ERR ErrEvictResource( const CKey& key, CResource* const pres, const BOOL fKeepHistory = fTrue );
void LockResourceForEvict( CResource* const pres, CLock* const plock ); void UnlockResourceForEvict( CLock* const plock );
void BeginResourceScan( CLock* const plock ); ERR ErrGetCurrentResource( CLock* const plock, CResource** const ppres ); ERR ErrGetNextResource( CLock* const plock, CResource** const ppres ); ERR ErrEvictCurrentResource( CLock* const plock, const CKey& key, const BOOL fKeepHistory = fTrue ); void EndResourceScan( CLock* const plock );
DWORD CHistoryRecord(); DWORD CHistoryHit(); DWORD CHistoryRequest();
DWORD CResourceScanned(); DWORD CResourceScannedOutOfOrder();
public:
// class containing the history of an evicted CResource instance
class CHistory { public:
CHistory() {} ~CHistory() {}
static SIZE_T OffsetOfAIIC() { return OffsetOf( CHistory, m_aiic ); }
public:
CApproximateIndex< TICK, CHistory, OffsetOfAIIC >::CInvasiveContext m_aiic; CKey m_key; TICK m_rgtick[ m_Kmax ]; }; private:
// index over CResources by LRUK
typedef CApproximateIndex< TICK, CResource, CInvasiveContext::OffsetOfAIIC > CResourceLRUK;
// index over history by LRUK
typedef CApproximateIndex< TICK, CHistory, CHistory::OffsetOfAIIC > CHistoryLRUK;
// list of resources that need to have their positions updated in the
// resource LRUK
typedef CInvasiveList< CResource, CInvasiveContext::OffsetOfILE > CUpdateList;
// list of resources that couldn't be placed in their correct positions
// in the resource LRUK
typedef CInvasiveList< CResource, CInvasiveContext::OffsetOfILE > CStuckList;
public:
// API Lock Context
class CLock { public:
CLock() {} ~CLock() {}
private:
friend class CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >;
CResourceLRUK::CLock m_lock; TICK m_tickIndexCurrent; CUpdateList m_UpdateList; CStuckList m_StuckList; CResource* m_presStuckList; CResource* m_presStuckListNext; };
// entry used in the hash table used to map CKeys to CHistorys
class CHistoryEntry { public:
CHistoryEntry() {} ~CHistoryEntry() {}
CHistoryEntry& operator=( const CHistoryEntry& he ) { m_KeySignature = he.m_KeySignature; m_phist = he.m_phist; return *this; }
public:
ULONG_PTR m_KeySignature; CHistory* m_phist; };
private:
// table that maps CKeys to CHistoryEntrys
typedef CDynamicHashTable< CKey, CHistoryEntry > CHistoryTable;
private:
void _TouchResource( CInvasiveContext* const pic, const TICK tickNow, const TICK tickLastBIExpected ); void _RestoreHistory( const CKey& key, CInvasiveContext* const pic, const TICK tickNow ); void _StoreHistory( const CKey& key, CInvasiveContext* const pic ); CHistory* _PhistAllocHistory(); CHistoryLRUK::ERR _ErrInsertHistory( CHistory* const phist ); CResource* _PresFromPic( CInvasiveContext* const pic ) const; CInvasiveContext* _PicFromPres( CResource* const pres ) const; static TICK _TickCurrentTime(); long _CmpTick( const TICK tick1, const TICK tick2 ) const;
private:
// never updated
BOOL m_fInitialized; DWORD m_K; TICK m_ctickCorrelatedTouch; TICK m_ctickTimeout; TICK m_dtickUncertainty; BYTE m_rgbReserved1[ 12 ];
// rarely updated
volatile DWORD m_cHistoryRecord; volatile DWORD m_cHistoryHit; volatile DWORD m_cHistoryReq; volatile DWORD m_cResourceScanned; volatile DWORD m_cResourceScannedOutOfOrder; BYTE m_rgbReserved2[ 12 ]; CHistoryLRUK m_HistoryLRUK; // BYTE m_rgbReserved3[ 0 ];
CHistoryTable m_KeyHistory; // BYTE m_rgbReserved4[ 0 ];
// commonly updated
CResourceLRUK m_ResourceLRUK; // BYTE m_rgbReserved5[ 0 ];
};
// ctor
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: CLRUKResourceUtilityManager( const int Rank ) : m_fInitialized( fFalse ), m_HistoryLRUK( Rank - 1 ), m_KeyHistory( Rank - 1 ), m_ResourceLRUK( Rank ) { }
// dtor
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ~CLRUKResourceUtilityManager() { }
// initializes the LRUK resource utility manager using the given parameters.
// if the RUM cannot be initialized, errOutOfMemory is returned
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ErrInit( const int K, const double csecCorrelatedTouch, const double csecTimeout, const double csecUncertainty, const double dblHashLoadFactor, const double dblHashUniformity, const double dblSpeedSizeTradeoff ) { // validate all parameters
if ( K < 1 || K > m_Kmax || csecCorrelatedTouch < 0 || csecTimeout < 0 || dblHashLoadFactor < 0 || dblHashUniformity < 1.0 ) { return errInvalidParameter; }
// init our parameters
m_K = DWORD( K ); m_ctickCorrelatedTouch = TICK( csecCorrelatedTouch * 1000.0 ); m_ctickTimeout = TICK( csecTimeout * 1000.0 ); m_dtickUncertainty = TICK( max( csecUncertainty * 1000.0, 2.0 ) );
// init our counters
m_cHistoryRecord = 0; m_cHistoryHit = 0; m_cHistoryReq = 0;
m_cResourceScanned = 0; m_cResourceScannedOutOfOrder = 0;
// init our indexes
//
// NOTE: the precision for the history LRUK is intentionally low so that
// we are forced to purge history records older than twice the timeout
// in order to insert new history records. we would normally only purge
// history records on demand which could allow a sudden burst of activity
// to permanently allocate an abnormally high number of history records
if ( m_HistoryLRUK.ErrInit( 4 * m_K * m_ctickTimeout, m_dtickUncertainty, dblSpeedSizeTradeoff ) != errSuccess ) { Term(); return errOutOfMemory; }
if ( m_KeyHistory.ErrInit( dblHashLoadFactor, dblHashUniformity ) != errSuccess ) { Term(); return errOutOfMemory; }
#ifdef DEBUG
const TICK dtickLRUKPrecision = 2 * m_K * m_ctickTimeout; #else // !DEBUG
#ifdef RTM
const TICK dtickLRUKPrecision = ~TICK( 0 ); // 49.7 days
#else // !RTM
const TICK dtickLRUKPrecision = 2 * 24 * 60 * 60 * 1000; // 2 days
#endif // RTM
#endif // DEBUG
if ( m_ResourceLRUK.ErrInit( dtickLRUKPrecision, m_dtickUncertainty, dblSpeedSizeTradeoff ) != errSuccess ) { Term(); return errOutOfMemory; }
m_fInitialized = fTrue; return errSuccess; }
// terminates the LRUK RUM. this function can be called even if the RUM has
// never been initialized or is only partially initialized
//
// NOTE: any data stored in the RUM at this time will be lost!
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: Term() { // purge all history records from the history LRUK. we must do this from
// the history LRUK and not the history table as it is possible for the
// history table to lose pointers to history records in the history LRUK.
// see ErrEvictCurrentResource for details
if ( m_fInitialized ) { CHistoryLRUK::CLock lockHLRUK;
m_HistoryLRUK.MoveBeforeFirst( &lockHLRUK ); while ( m_HistoryLRUK.ErrMoveNext( &lockHLRUK ) == errSuccess ) { CHistory* phist; CHistoryLRUK::ERR errHLRUK = m_HistoryLRUK.ErrRetrieveEntry( &lockHLRUK, &phist ); RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess );
errHLRUK = m_HistoryLRUK.ErrDeleteEntry( &lockHLRUK ); RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess );
delete phist; } m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK ); } // terminate our indexes
m_ResourceLRUK.Term(); m_KeyHistory.Term(); m_HistoryLRUK.Term();
m_fInitialized = fFalse; }
// starts management of the specified resource optionally using any previous
// knowledge the RUM has about this resource. the resource must currently be
// evicted from the RUM. the resource will be touched by this call. if we
// cannot start management of this resource, errOutOfMemory will be returned
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ErrCacheResource( const CKey& key, CResource* const pres, const BOOL fUseHistory ) { ERR err = errSuccess; CLock lock; CResourceLRUK::ERR errLRUK;
CInvasiveContext* const pic = _PicFromPres( pres );
// initialize this resource to be touched once by setting all of its
// touch times to the current time
const TICK tickNow = _TickCurrentTime();
for ( int K = 1; K <= m_Kmax; K++ ) { pic->m_rgtick[ K - 1 ] = tickNow; } pic->m_tickLast = tickNow;
// we are supposed to use the history for this resource if available
if ( fUseHistory ) { // restore the history for this resource
_RestoreHistory( key, pic, tickNow ); }
// insert this resource into the resource LRUK at its Kth touch time or as
// close as we can get to that Kth touch time
do { const TICK tickK = pic->m_rgtick[ m_K - 1 ]; m_ResourceLRUK.LockKeyPtr( tickK, pres, &lock.m_lock );
pic->m_tickIndex = tickK; errLRUK = m_ResourceLRUK.ErrInsertEntry( &lock.m_lock, pres, fTrue );
if ( errLRUK != CResourceLRUK::errSuccess ) { pic->m_tickIndex = tickNil; m_ResourceLRUK.UnlockKeyPtr( &lock.m_lock );
const TICK tickYoungest = m_ResourceLRUK.KeyInsertMost(); m_ResourceLRUK.LockKeyPtr( tickYoungest, pres, &lock.m_lock );
pic->m_tickIndex = tickYoungest; errLRUK = m_ResourceLRUK.ErrInsertEntry( &lock.m_lock, pres, fTrue );
if ( errLRUK != CResourceLRUK::errSuccess ) { pic->m_tickIndex = tickNil; } }
m_ResourceLRUK.UnlockKeyPtr( &lock.m_lock ); } while ( errLRUK == CResourceLRUK::errKeyRangeExceeded ); RESMGRAssert( errLRUK == CResourceLRUK::errSuccess || errLRUK == CResourceLRUK::errOutOfMemory );
return errLRUK == CResourceLRUK::errSuccess ? errSuccess : errOutOfMemory; }
// touches the specifed resource according to the LRUK Replacement Policy
//
// NOTE: only the touch times are updated here. the actual update of this
// resource's position in the LRUK index is amortized to ErrGetNextResource().
// this scheme minimizes updates to the index and localizes those updates
// to the thread(s) that perform cleanup of the resources. this scheme also
// makes it impossible to block while touching a resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: TouchResource( CResource* const pres, const TICK tickNow ) { CInvasiveContext* const pic = _PicFromPres( pres );
// get the current last touch time of the resource. we will use this time
// to lock the resource for touching. if the last touch time ever changes
// to not match this touch time then we can no longer touch the resource
//
// NOTE: munge the last tick time so that it will never match tickNil
const TICK tickLastBIExpected = pic->m_tickLast & tickMask;
// if the current time is equal to the last touch time then there is no
// need to touch the resource
//
// NOTE: also handle the degenerate case where the current time is less
// than the last touch time
if ( _CmpTick( tickNow, tickLastBIExpected ) <= 0 ) { // do not touch the resource
}
// if the current time is not equal to the last touch time then we need
// to update the touch times of the resource
else { _TouchResource( pic, tickNow, tickLastBIExpected ); } }
// returns fTrue if the specified resource is touched frequently
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline BOOL CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: FHotResource( CResource* const pres ) { CInvasiveContext* const pic = _PicFromPres( pres );
// a resource is hot if it's Kth touch time is within the Kth time region
// in the index
const TICK tickNow = _TickCurrentTime(); const TICK tickKTimeout = ( m_K - 2 ) * m_ctickTimeout; const TICK tickKHot = tickNow + tickKTimeout;
return _CmpTick( pic->m_rgtick[ m_K - 1 ], tickKHot ) > 0; }
// stops management of the specified resource optionally saving any current
// knowledge the RUM has about this resource. if the resource is not currently
// cached by the RUM, errResourceNotCached will be returned
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ErrEvictResource( const CKey& key, CResource* const pres, const BOOL fKeepHistory ) { ERR err = errSuccess; CLock lock;
// lock and evict this resource from the resource LRUK by setting up our
// currency to look like we navigated to this resource normally and then
// calling our normal eviction routine
LockResourceForEvict( pres, &lock ); if ( ( err = ErrEvictCurrentResource( &lock, key, fKeepHistory ) ) != errSuccess ) { RESMGRAssert( err == errNoCurrentResource );
err = errResourceNotCached; }
UnlockResourceForEvict( &lock ); return err; }
// sets up the specified lock context in preparation for evicting the given
// resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: LockResourceForEvict( CResource* const pres, CLock* const plock ) { // get the current index touch time for this resource
CInvasiveContext* const pic = _PicFromPres( pres );
const TICK tickIndex = pic->m_tickIndex;
// lock this resource for this Kth touch time in the LRUK
m_ResourceLRUK.LockKeyPtr( tickIndex, pres, &plock->m_lock );
// the index touch time has changed for this resource or it is tickNil
if ( pic->m_tickIndex != tickIndex || tickIndex == tickNil ) { // release our lock and lock tickNil for this resource. we know that
// we will never find a resource in this bucket so we will have the
// desired errNoCurrentResource from ErrEvictCurrentResource
m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); m_ResourceLRUK.MoveBeforeKeyPtr( tickNil, NULL, &plock->m_lock ); } }
// releases the lock acquired with LockResourceForEvict()
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: UnlockResourceForEvict( CLock* const plock ) { m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); }
// sets up the specified lock context in preparation for scanning all resources
// managed by the RUM by ascending utility
//
// NOTE: this function will acquire a lock that must eventually be released
// via EndResourceScan()
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: BeginResourceScan( CLock* const plock ) { // move before the first entry in the resource LRUK
m_ResourceLRUK.MoveBeforeFirst( &plock->m_lock ); }
// retrieves the current resource managed by the RUM by ascending utility
// locked by the specified lock context. if there is no current resource,
// errNoCurrentResource is returned
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ErrGetCurrentResource( CLock* const plock, CResource** const ppres ) { // we are currently walking the resource LRUK
if ( plock->m_StuckList.FEmpty() ) { CResourceLRUK::ERR errLRUK; // get the current resource in the resource LRUK
errLRUK = m_ResourceLRUK.ErrRetrieveEntry( &plock->m_lock, ppres );
// return the status of our currency
return errLRUK == CResourceLRUK::errSuccess ? errSuccess : errNoCurrentResource; }
// we are currently walking the list of resources that couldn't be
// moved
else { // return the status of our currency on the stuck list
*ppres = plock->m_presStuckList; return *ppres ? errSuccess : errNoCurrentResource; } }
// retrieves the next resource managed by the RUM by ascending utility locked
// by the specified lock context. if there are no more resources to be
// scanned, errNoCurrentResource is returned
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ErrGetNextResource( CLock* const plock, CResource** const ppres ) { CResourceLRUK::ERR errLRUK; // keep scanning until we establish currency on a resource or reach the
// end of the resource LRUK
m_cResourceScanned++;
*ppres = NULL; errLRUK = CResourceLRUK::errSuccess; do { // we are currently walking the resource LRUK
if ( plock->m_StuckList.FEmpty() ) { // scan forward in the index until we find the least useful
// resource that is in the correct place in the index or we reach
// the end of the index
while ( ( errLRUK = m_ResourceLRUK.ErrMoveNext( &plock->m_lock ) ) == CResourceLRUK::errSuccess ) { CResourceLRUK::ERR errLRUK2 = CResourceLRUK::errSuccess; CResource* pres; errLRUK2 = m_ResourceLRUK.ErrRetrieveEntry( &plock->m_lock, &pres ); RESMGRAssert( errLRUK2 == CResourceLRUK::errSuccess );
CInvasiveContext* const pic = _PicFromPres( pres );
// if our update list contains resources and the tickIndex for
// those resources doesn't match the tickIndex for this
// resource then we need to stop and empty the update list
if ( !plock->m_UpdateList.FEmpty() && m_ResourceLRUK.CmpKey( pic->m_tickIndex, plock->m_tickIndexCurrent ) ) { break; }
// if the Kth touch time of this resource matches the tickIndex
// of the resource then we can return this resource as the
// current resource because it is in the correct place in the
// index
if ( !m_ResourceLRUK.CmpKey( pic->m_tickIndex, pic->m_rgtick[ m_K - 1 ] ) ) { *ppres = pres; break; }
// if the Kth touch time of this resource doesn't match the
// tickIndex of the resource then add the resource to the
// update list so that we can move it to the correct place
// later. leave space reserved for this resource here so
// that we can always move it back in case of an error
errLRUK2 = m_ResourceLRUK.ErrReserveEntry( &plock->m_lock ); RESMGRAssert( errLRUK2 == CResourceLRUK::errSuccess );
errLRUK2 = m_ResourceLRUK.ErrDeleteEntry( &plock->m_lock ); RESMGRAssert( errLRUK2 == CResourceLRUK::errSuccess );
plock->m_tickIndexCurrent = pic->m_tickIndex; pic->m_tickIndex = tickNil; plock->m_UpdateList.InsertAsNextMost( pres ); } RESMGRAssert( errLRUK == CResourceLRUK::errSuccess || errLRUK == CResourceLRUK::errNoCurrentEntry );
// we still do not have a current resource
if ( *ppres == NULL ) { // release our lock on the resource LRUK
m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); // try to update the positions of any resources we have in our
// update list. remove the reservation for any resource we
// can move and put any resource we can't move in the stuck
// list
while ( !plock->m_UpdateList.FEmpty() ) { CResource* const pres = plock->m_UpdateList.PrevMost(); CInvasiveContext* const pic = _PicFromPres( pres ); plock->m_UpdateList.Remove( pres );
CResourceLRUK::ERR errLRUK2; do { const TICK tickK = pic->m_rgtick[ m_K - 1 ]; m_ResourceLRUK.LockKeyPtr( tickK, pres, &plock->m_lock );
pic->m_tickIndex = tickK; errLRUK2 = m_ResourceLRUK.ErrInsertEntry( &plock->m_lock, pres, fTrue );
if ( errLRUK2 != CResourceLRUK::errSuccess ) { pic->m_tickIndex = tickNil; m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock );
const TICK tickYoungest = m_ResourceLRUK.KeyInsertMost(); m_ResourceLRUK.LockKeyPtr( tickYoungest, pres, &plock->m_lock );
pic->m_tickIndex = tickYoungest; if ( m_ResourceLRUK.CmpKey( pic->m_tickIndex, plock->m_tickIndexCurrent ) ) { errLRUK2 = m_ResourceLRUK.ErrInsertEntry( &plock->m_lock, pres, fTrue ); } else { errLRUK2 = CResourceLRUK::errOutOfMemory; }
if ( errLRUK2 != CResourceLRUK::errSuccess ) { pic->m_tickIndex = tickNil; } }
m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); } while ( errLRUK2 == CResourceLRUK::errKeyRangeExceeded );
if ( errLRUK2 == CResourceLRUK::errSuccess ) { m_ResourceLRUK.LockKeyPtr( plock->m_tickIndexCurrent, pres, &plock->m_lock ); m_ResourceLRUK.UnreserveEntry( &plock->m_lock ); m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock );
errLRUK = CResourceLRUK::errSuccess; } else { plock->m_StuckList.InsertAsNextMost( pres ); } }
// set our currency so that if we have any resources in the
// stuck list then we will walk them as if they are in the
// correct place in the resource LRUK
if ( plock->m_StuckList.FEmpty() ) { m_ResourceLRUK.MoveAfterKeyPtr( plock->m_tickIndexCurrent, (CResource*)~NULL, &plock->m_lock ); } else { plock->m_presStuckList = NULL; plock->m_presStuckListNext = plock->m_StuckList.PrevMost(); } } }
// we are currently walking the list of resources that couldn't be
// moved
if ( !plock->m_StuckList.FEmpty() ) { // move to the next resource in the list
plock->m_presStuckList = ( plock->m_presStuckList ? plock->m_StuckList.Next( plock->m_presStuckList ) : plock->m_presStuckListNext ); plock->m_presStuckListNext = NULL;
// we have a current resource
if ( plock->m_presStuckList ) { // return the current resource
m_cResourceScannedOutOfOrder++;
*ppres = plock->m_presStuckList; }
// we still do not have a current resource
else { // we have walked off of the end of the list of resources that
// couldn't be moved so we need to put them back into their
// source buckets before we move on to the next entry in the
// resource LRUK. we are guaranteed to be able to put them
// back because we reserved space for them
while ( !plock->m_StuckList.FEmpty() ) { CResource* pres = plock->m_StuckList.PrevMost(); CResourceLRUK::ERR errLRUK2 = CResourceLRUK::errSuccess;
plock->m_StuckList.Remove( pres ); m_ResourceLRUK.LockKeyPtr( plock->m_tickIndexCurrent, pres, &plock->m_lock ); m_ResourceLRUK.UnreserveEntry( &plock->m_lock );
_PicFromPres( pres )->m_tickIndex = plock->m_tickIndexCurrent; errLRUK2 = m_ResourceLRUK.ErrInsertEntry( &plock->m_lock, pres, fTrue ); RESMGRAssert( errLRUK2 == CResourceLRUK::errSuccess ); m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); }
// we are no longer walking the stuck list so restore our
// currency on the resource LRUK
m_ResourceLRUK.MoveAfterKeyPtr( plock->m_tickIndexCurrent, (CResource*)~NULL, &plock->m_lock ); } } } while ( *ppres == NULL && errLRUK != CResourceLRUK::errNoCurrentEntry );
// return the state of our currency
return *ppres ? errSuccess : errNoCurrentResource; }
// stops management of the current resource optionally saving any current
// knowledge the RUM has about this resource. if there is no current
// resource, errNoCurrentResource will be returned
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: ErrEvictCurrentResource( CLock* const plock, const CKey& key, const BOOL fKeepHistory ) { ERR err; CResourceLRUK::ERR errLRUK; CResource* pres;
// get the current resource in the resource LRUK, if any
if ( ( err = ErrGetCurrentResource( plock, &pres ) ) != errSuccess ) { return err; } // we are currently walking the resource LRUK
if ( plock->m_StuckList.FEmpty() ) { // delete the current resource from the resource LRUK
errLRUK = m_ResourceLRUK.ErrDeleteEntry( &plock->m_lock ); RESMGRAssert( errLRUK == CResourceLRUK::errSuccess ); }
// we are currently walking the list of resources that couldn't be
// moved
else { // delete the current resource from the stuck list and remove its
// reservation from the resource LRUK
plock->m_presStuckListNext = plock->m_StuckList.Next( pres ); plock->m_StuckList.Remove( pres ); plock->m_presStuckList = NULL; m_ResourceLRUK.LockKeyPtr( plock->m_tickIndexCurrent, pres, &plock->m_lock ); m_ResourceLRUK.UnreserveEntry( &plock->m_lock ); m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock );
// if we are no longer walking the stuck list then restore our currency
// on the resource LRUK
if ( plock->m_StuckList.FEmpty() ) { m_ResourceLRUK.MoveAfterKeyPtr( plock->m_tickIndexCurrent, (CResource*)~NULL, &plock->m_lock ); } }
// we are to keep history for this resource
if ( fKeepHistory ) { // store the history for this resource
_StoreHistory( key, _PicFromPres( pres ) ); } return errSuccess; }
// ends the scan of all resources managed by the RUM by ascending utility
// associated with the specified lock context and releases all locks held
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: EndResourceScan( CLock* const plock ) { // we are currently walking the resource LRUK
if ( plock->m_StuckList.FEmpty() ) { // release our lock on the resource LRUK
m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); // try to update the positions of any resources we have in our
// update list. remove the reservation for any resource we
// can move and put any resource we can't move in the stuck
// list
while ( !plock->m_UpdateList.FEmpty() ) { CResource* const pres = plock->m_UpdateList.PrevMost(); CInvasiveContext* const pic = _PicFromPres( pres ); plock->m_UpdateList.Remove( pres );
CResourceLRUK::ERR errLRUK; do { const TICK tickK = pic->m_rgtick[ m_K - 1 ]; m_ResourceLRUK.LockKeyPtr( tickK, pres, &plock->m_lock );
pic->m_tickIndex = tickK; errLRUK = m_ResourceLRUK.ErrInsertEntry( &plock->m_lock, pres, fTrue );
if ( errLRUK != CResourceLRUK::errSuccess ) { pic->m_tickIndex = tickNil; m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock );
const TICK tickYoungest = m_ResourceLRUK.KeyInsertMost(); m_ResourceLRUK.LockKeyPtr( tickYoungest, pres, &plock->m_lock );
pic->m_tickIndex = tickYoungest; errLRUK = m_ResourceLRUK.ErrInsertEntry( &plock->m_lock, pres, fTrue );
if ( errLRUK != CResourceLRUK::errSuccess ) { pic->m_tickIndex = tickNil; } }
m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); } while ( errLRUK == CResourceLRUK::errKeyRangeExceeded );
if ( errLRUK == CResourceLRUK::errSuccess ) { m_ResourceLRUK.LockKeyPtr( plock->m_tickIndexCurrent, pres, &plock->m_lock ); m_ResourceLRUK.UnreserveEntry( &plock->m_lock ); m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); } else { plock->m_StuckList.InsertAsNextMost( pres ); } } }
// put all the resources we were not able to move back into their
// source buckets. we know that we will succeed because we reserved
// space for them
while ( !plock->m_StuckList.FEmpty() ) { CResource* pres = plock->m_StuckList.PrevMost(); CResourceLRUK::ERR errLRUK = CResourceLRUK::errSuccess;
plock->m_StuckList.Remove( pres ); m_ResourceLRUK.LockKeyPtr( plock->m_tickIndexCurrent, pres, &plock->m_lock ); m_ResourceLRUK.UnreserveEntry( &plock->m_lock );
_PicFromPres( pres )->m_tickIndex = plock->m_tickIndexCurrent; errLRUK = m_ResourceLRUK.ErrInsertEntry( &plock->m_lock, pres, fTrue ); RESMGRAssert( errLRUK == CResourceLRUK::errSuccess ); m_ResourceLRUK.UnlockKeyPtr( &plock->m_lock ); } }
// returns the current number of resource history records retained for
// supporting the LRUK replacement policy
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline DWORD CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: CHistoryRecord() { return m_cHistoryRecord; }
// returns the cumulative number of successful resource history record loads
// when caching a resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline DWORD CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: CHistoryHit() { return m_cHistoryHit; }
// returns the cumulative number of attempted resource history record loads
// when caching a resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline DWORD CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: CHistoryRequest() { return m_cHistoryReq; }
// returns the cumulative number of resources scanned in the LRUK
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline DWORD CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: CResourceScanned() { return m_cResourceScanned; }
// returns the cumulative number of resources scanned in the LRUK that were
// returned out of order
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline DWORD CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: CResourceScannedOutOfOrder() { return m_cResourceScannedOutOfOrder; }
// updates the touch times of a given resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _TouchResource( CInvasiveContext* const pic, const TICK tickNow, const TICK tickLastBIExpected ) { RESMGRAssert( tickLastBIExpected != tickLastBIExpected || _CmpTick( tickLastBIExpected, tickNow ) < 0 ); // if the current time and the last historical touch time are closer than
// the correlation interval then this is a correlated touch
if ( tickNow - pic->m_rgtick[ 1 - 1 ] <= m_ctickCorrelatedTouch ) { // update the last tick time to the current time iff the last touch time
// has not changed and is not tickNil
AtomicCompareExchange( (long*)&pic->m_tickLast, tickLastBIExpected, tickNow ); }
// if the current time and the last historical touch time are not closer
// than the correlation interval then this is not a correlated touch
else { // to determine who will get to actually touch the resource, we will do an
// AtomicCompareExchange on the last touch time with tickNil. the thread
// whose transaction succeeds wins the race and will touch the resource.
// all other threads will leave. this is certainly acceptable as touches
// that collide are obviously too close together to be of any interest
const TICK tickLastBI = TICK( AtomicCompareExchange( (long*)&pic->m_tickLast, tickLastBIExpected, tickNil ) );
if ( tickLastBI == tickLastBIExpected ) { // compute the correlation period of the last series of correlated
// touches by taking the different between the last touch time and
// the most recent touch time in our history
RESMGRAssert( _CmpTick( tickLastBI, pic->m_rgtick[ 1 - 1 ] ) >= 0 );
const TICK dtickCorrelationPeriod = tickLastBI - pic->m_rgtick[ 1 - 1 ]; // move all touch times up one slot and advance them by the correlation
// period and the timeout. this will collapse all correlated touches
// to a time interval of zero and will make resources that have more
// uncorrelated touches appear younger in the index
for ( int K = m_Kmax; K >= 2; K-- ) { pic->m_rgtick[ K - 1 ] = pic->m_rgtick[ ( K - 1 ) - 1 ] + dtickCorrelationPeriod + m_ctickTimeout; }
// set the most recent historical touch time to the current time
pic->m_rgtick[ 1 - 1 ] = tickNow;
// set the last touch time to the current time via AtomicExchange,
// unlocking this resource to touches by other threads
AtomicExchange( (long*)&pic->m_tickLast, tickNow ); } } }
// restores the touch history for the specified resource if known
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _RestoreHistory( const CKey& key, CInvasiveContext* const pic, const TICK tickNow ) { CHistoryTable::CLock lockHist; CHistoryEntry he;
// this resource has history
m_cHistoryReq++;
m_KeyHistory.ReadLockKey( key, &lockHist ); if ( m_KeyHistory.ErrRetrieveEntry( &lockHist, &he ) == CHistoryTable::errSuccess ) { // determine the range of Kth touch times over which retained history
// is still valid
const TICK tickKHistoryMax = tickNow + ( m_K - 1 ) * m_ctickTimeout; const TICK tickKHistoryMin = tickNow - m_ctickTimeout; // the history is still valid
if ( m_HistoryLRUK.CmpKey( tickKHistoryMin, he.m_phist->m_rgtick[ m_K - 1 ] ) <= 0 && m_HistoryLRUK.CmpKey( he.m_phist->m_rgtick[ m_K - 1 ], tickKHistoryMax ) < 0 ) { // restore the history for this resource
m_cHistoryHit++;
pic->m_tickIndex = tickNil; for ( int K = 1; K <= m_Kmax; K++ ) { pic->m_rgtick[ K - 1 ] = he.m_phist->m_rgtick[ K - 1 ]; } pic->m_tickLast = he.m_phist->m_rgtick[ 1 - 1 ];
// touch this resource
TouchResource( _PresFromPic( pic ), tickNow ); } } m_KeyHistory.ReadUnlockKey( &lockHist ); }
// stores the touch history for a resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline void CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _StoreHistory( const CKey& key, CInvasiveContext* const pic ) { CHistoryLRUK::ERR errHLRUK = CHistoryLRUK::errSuccess; CHistory* phist = NULL;
// keep trying to store our history until we either succeed or fail with
// out of memory. commonly, this will succeed immediately but in rare cases
// we have to loop to resolve an errKeyRangeExceeded in the history LRUK
do { // determine the range of Kth touch times over which retained history
// is still valid
const TICK tickNow = _TickCurrentTime(); const TICK tickKHistoryMax = tickNow + ( m_K - 1 ) * m_ctickTimeout; const TICK tickKHistoryMin = tickNow - m_ctickTimeout; // only store the history for this resource if it is not too young or too
// old. history records that are too young or too old will not help us
// anyway
//
// NOTE: the only time we will ever see a history record that is too young
// is when a resource is evicted that hasn't been touched for so long that
// we have wrapped-around
if ( m_HistoryLRUK.CmpKey( tickKHistoryMin, pic->m_rgtick[ m_K - 1 ] ) <= 0 && m_HistoryLRUK.CmpKey( pic->m_rgtick[ m_K - 1 ], tickKHistoryMax ) < 0 ) { // we allocated a history record
if ( phist = _PhistAllocHistory() ) { // save the history for this resource
phist->m_key = key; for ( int K = 1; K <= m_Kmax; K++ ) { phist->m_rgtick[ K - 1 ] = pic->m_rgtick[ K - 1 ]; }
// we failed to insert the history in the history table
if ( ( errHLRUK = _ErrInsertHistory( phist ) ) != CHistoryLRUK::errSuccess ) { // free the history record
delete phist; } }
// we failed to allocate a history record
else { // set an out of memory condition
errHLRUK = CHistoryLRUK::errOutOfMemory; } }
// we have decided that the history for this resource is not useful
else { // claim success but do not store the history
errHLRUK = CHistoryLRUK::errSuccess; } } while ( errHLRUK == CHistoryLRUK::errKeyRangeExceeded ); }
// allocates a new history record to store the touch history for a resource.
// this history record can either be newly allocated or recycled from the
// history table
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::CHistory* CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _PhistAllocHistory() { CHistoryLRUK::ERR errHLRUK; CHistoryLRUK::CLock lockHLRUK;
CHistory* phist; CHistoryTable::ERR errHist; CHistoryTable::CLock lockHist;
// determine the range of Kth touch times over which retained history is
// still valid
const TICK tickNow = _TickCurrentTime(); const TICK tickKHistoryMax = tickNow + ( m_K - 1 ) * m_ctickTimeout; const TICK tickKHistoryMin = tickNow - m_ctickTimeout; // get the first history in the history LRUK, if any
m_HistoryLRUK.MoveBeforeFirst( &lockHLRUK ); if ( ( errHLRUK = m_HistoryLRUK.ErrMoveNext( &lockHLRUK ) ) == CHistoryLRUK::errSuccess ) { // if this history is no longer valid, recycle it
errHLRUK = m_HistoryLRUK.ErrRetrieveEntry( &lockHLRUK, &phist ); RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess );
if ( m_HistoryLRUK.CmpKey( phist->m_rgtick[ m_K - 1 ], tickKHistoryMin ) < 0 || m_HistoryLRUK.CmpKey( tickKHistoryMax, phist->m_rgtick[ m_K - 1 ] ) <= 0 ) { // remove this history from the history LRUK
errHLRUK = m_HistoryLRUK.ErrDeleteEntry( &lockHLRUK ); RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess );
AtomicDecrement( (long*)&m_cHistoryRecord );
m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK );
// try to remove this history from the history table, but don't
// worry about it if it is not there
//
// NOTE: the history record must exist until we unlock its key
// in the history table after we delete its entry so that some
// other thread can't touch a deleted history record
m_KeyHistory.WriteLockKey( phist->m_key, &lockHist ); errHist = m_KeyHistory.ErrDeleteEntry( &lockHist ); RESMGRAssert( errHist == CHistoryTable::errSuccess || errHist == CHistoryTable::errNoCurrentEntry );
m_KeyHistory.WriteUnlockKey( &lockHist );
// return this history
return phist; } } RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess || errHLRUK == CHistoryLRUK::errNoCurrentEntry ); m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK ); // get the last history in the history LRUK, if any
m_HistoryLRUK.MoveAfterLast( &lockHLRUK ); if ( ( errHLRUK = m_HistoryLRUK.ErrMovePrev( &lockHLRUK ) ) == CHistoryLRUK::errSuccess ) { // if this history is no longer valid, recycle it
errHLRUK = m_HistoryLRUK.ErrRetrieveEntry( &lockHLRUK, &phist ); RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess );
if ( m_HistoryLRUK.CmpKey( phist->m_rgtick[ m_K - 1 ], tickKHistoryMin ) < 0 || m_HistoryLRUK.CmpKey( tickKHistoryMax, phist->m_rgtick[ m_K - 1 ] ) <= 0 ) { // remove this history from the history LRUK
errHLRUK = m_HistoryLRUK.ErrDeleteEntry( &lockHLRUK ); RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess );
AtomicDecrement( (long*)&m_cHistoryRecord );
m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK );
// try to remove this history from the history table, but don't
// worry about it if it is not there
//
// NOTE: the history record must exist until we unlock its key
// in the history table after we delete its entry so that some
// other thread can't touch a deleted history record
m_KeyHistory.WriteLockKey( phist->m_key, &lockHist ); errHist = m_KeyHistory.ErrDeleteEntry( &lockHist ); RESMGRAssert( errHist == CHistoryTable::errSuccess || errHist == CHistoryTable::errNoCurrentEntry );
m_KeyHistory.WriteUnlockKey( &lockHist );
// return this history
return phist; } } RESMGRAssert( errHLRUK == CHistoryLRUK::errSuccess || errHLRUK == CHistoryLRUK::errNoCurrentEntry ); m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK );
// allocate and return a new history because one couldn't be recycled
return new CHistory; }
// inserts the history record containing the touch history for a resource into
// the history table
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::CHistoryLRUK::ERR CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _ErrInsertHistory( CHistory* const phist ) { CHistoryEntry he;
CHistoryTable::ERR errHist; CHistoryTable::CLock lockHist;
CHistoryLRUK::ERR errHLRUK; CHistoryLRUK::CLock lockHLRUK;
// try to insert our history into the history table
he.m_KeySignature = CHistoryTable::CKeyEntry::Hash( phist->m_key ); he.m_phist = phist;
m_KeyHistory.WriteLockKey( phist->m_key, &lockHist ); if ( ( errHist = m_KeyHistory.ErrInsertEntry( &lockHist, he ) ) != CHistoryTable::errSuccess ) { // there is already an entry in the history table for this resource
if ( errHist == CHistoryTable::errKeyDuplicate ) { // replace this entry with our entry. note that this will make the
// old history that this entry pointed to invisible to the history
// table. this is OK because we will eventually clean it up via the
// history LRUK
errHist = m_KeyHistory.ErrReplaceEntry( &lockHist, he ); RESMGRAssert( errHist == CHistoryTable::errSuccess ); }
// some other error occurred while inserting this entry in the history table
else { RESMGRAssert( errHist == CHistoryTable::errOutOfMemory ); m_KeyHistory.WriteUnlockKey( &lockHist ); return CHistoryLRUK::errOutOfMemory; } } m_KeyHistory.WriteUnlockKey( &lockHist );
// try to insert our history into the history LRUK
m_HistoryLRUK.LockKeyPtr( phist->m_rgtick[ m_K - 1 ], phist, &lockHLRUK ); if ( ( errHLRUK = m_HistoryLRUK.ErrInsertEntry( &lockHLRUK, phist ) ) != CHistoryLRUK::errSuccess ) { RESMGRAssert( errHLRUK == CHistoryLRUK::errOutOfMemory || errHLRUK == CHistoryLRUK::errKeyRangeExceeded ); // we failed to insert our history into the history LRUK
m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK );
m_KeyHistory.WriteLockKey( phist->m_key, &lockHist ); errHist = m_KeyHistory.ErrDeleteEntry( &lockHist ); RESMGRAssert( errHist == CHistoryTable::errSuccess );
m_KeyHistory.WriteUnlockKey( &lockHist ); return errHLRUK; } m_HistoryLRUK.UnlockKeyPtr( &lockHLRUK );
// we have successfully stored our history
AtomicIncrement( (long*)&m_cHistoryRecord ); return CHistoryLRUK::errSuccess; }
// converts a pointer to the invasive context to a pointer to a resource
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CResource* CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _PresFromPic( CInvasiveContext* const pic ) const { return (CResource*)( (BYTE*)pic - OffsetOfIC() ); }
// converts a pointer to a resource to a pointer to the invasive context
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::CInvasiveContext* CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _PicFromPres( CResource* const pres ) const { return (CInvasiveContext*)( (BYTE*)pres + OffsetOfIC() ); }
// returns the current tick count in msec resolution updated every 2 msec with
// the special lock value 0xFFFFFFFF reserved
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >::TICK CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _TickCurrentTime() { return TICK( DWORD( DwOSTimeGetTickCount() ) & tickMask ); }
// performs a wrap-around insensitive comparison of two TICKs
template< int m_Kmax, class CResource, PfnOffsetOf OffsetOfIC, class CKey > inline long CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey >:: _CmpTick( const TICK tick1, const TICK tick2 ) const { return long( tick1 - tick2 ); }
#define DECLARE_LRUK_RESOURCE_UTILITY_MANAGER( m_Kmax, CResource, OffsetOfIC, CKey, Typedef ) \
\ typedef CLRUKResourceUtilityManager< m_Kmax, CResource, OffsetOfIC, CKey > Typedef; \ \ DECLARE_APPROXIMATE_INDEX( Typedef::TICK, CResource, Typedef::CInvasiveContext::OffsetOfAIIC, Typedef##__m_aiResourceLRUK ); \ \ DECLARE_APPROXIMATE_INDEX( Typedef::TICK, Typedef::CHistory, Typedef::CHistory::OffsetOfAIIC, Typedef##__m_aiHistoryLRU ); \ \ inline ULONG_PTR Typedef::CHistoryTable::CKeyEntry:: \ Hash() const \ { \ return ULONG_PTR( m_entry.m_KeySignature ); \ } \ \ inline BOOL Typedef::CHistoryTable::CKeyEntry:: \ FEntryMatchesKey( const CKey& key ) const \ { \ return Hash() == Hash( key ) && m_entry.m_phist->m_key == key; \ } \ \ inline void Typedef::CHistoryTable::CKeyEntry:: \ SetEntry( const Typedef::CHistoryEntry& he ) \ { \ m_entry = he; \ } \ \ inline void Typedef::CHistoryTable::CKeyEntry:: \ GetEntry( Typedef::CHistoryEntry* const phe ) const \ { \ *phe = m_entry; \ }
}; // namespace RESMGR
using namespace RESMGR;
#endif // _RESMGR_HXX_INCLUDED
|