|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionA LRU Cache is a key-value based data container that is constrained by size and/or age, removing the least recently used objects first. This algorithm requires keeping track of the most recent time each object is accessed, which can be expensive to ensure the algorithm always discards the least recently used item. Other LRU AlgorithmsMost LRU Caching algorithms consist of two parts: a dictionary and a list. The dictionary guarantees quick access to your data, and the list, ordered by age of the objects, controls the lifespan of objects and determines which objects are to be removed first. A simple LRU Cache implementation uses a doubly linked list; adding new items to the head, removing items from the tail, and moving any existing items to the head when referenced (touched). This algorithm is good for single threaded applications but becomes very slow in a multi-threaded environment. In a multi-threaded app, every time the linked list is modified, it must be locked. This requires thread locks on insert, read, and delete operations, causing the slowness. Another algorithm is to mark each item’s age with a timestamp ( OverviewThe algorithm I choose, divides items into time slices I call In this system, even though the exact order of items is not maintained, this is still a true LRU Cache because everything that remains in the oldest bag (after moves are made for the touched items) is removed at the same time. Therefore, no item remains in the cache that is older than the removed items. My implementation of Instead of storing item nodes in the LockingBefore I get into the code, I should provide a brief explanation of all the locking mechanisms used by the The first and simplest is the The next locking mechanism is the if(Monitor.TryEnter(obj))
try {
//do something if lock is not busy
}
finally {
Monitor.Exit(obj);
}
The last locking mechanism used is Code ReviewBecause the public void Touch()
{
if( _value != null && ageBag != _mgr._currentBag )
{
if( ageBag == null )
lock( _mgr )
if( ageBag == null )
{
// if node.AgeBag==null then the object is not
// currently managed by LifespanMgr so add it
next = _mgr._currentBag.first;
_mgr._currentBag.first = this;
Interlocked.Increment( ref _mgr._owner._curCount );
}
ageBag = _mgr._currentBag;
Interlocked.Increment( ref _mgr._currentSize );
}
_mgr.CheckValid();
}
Each time an indexed item is added or referenced, public void CheckValid()
{
DateTime now = DateTime.Now;
// if lock is currently held then skip and let next Touch perform cleanup.
if( (_currentSize > _bagItemLimit || now > _nextValidCheck)
&& Monitor.TryEnter( this ) )
try
{
if( (_currentSize > _bagItemLimit || now > _nextValidCheck) )
{
// if cache is no longer valid throw contents
// away and start over, else cleanup old items
if( _current > 1000000 || (_owner._isValid != null
&& !_owner._isValid()) )
_owner.Clear();
else
CleanUp( now );
}
}
finally
{
Monitor.Exit( this );
}
}
public void CleanUp( DateTime now )
{
if( _current != _oldest )
lock( this )
{
//calculate how many items should be removed
DateTime maxAge = now.Subtract( _maxAge );
DateTime minAge = now.Subtract( _minAge );
int itemsToRemove = _owner._curCount - _owner._capacity;
AgeBag bag = _bags[_oldest % _size];
while( _current != _oldest && (_current-_oldest>_size - 5
|| bag.startTime < maxAge || (itemsToRemove > 0
&& bag.stopTime > minAge)) )
{
// cache is still too big / old so remove oldest bag
Node node = bag.first;
bag.first = null;
while( node != null )
{
Node next = node.next;
node.next = null;
if( node.Value != null && node.ageBag != null )
if( node.ageBag == bag )
{
// item has not been touched since bag was
// closed, so remove it from LifespanMgr
++itemsToRemove;
node.ageBag = null;
Interlocked.Decrement( ref _owner._curCount );
}
else
{
// item has been touched and should
// be moved to correct age bag now
node.next = node.ageBag.first;
node.ageBag.first = node;
}
node = next;
}
// increment oldest bag
bag = _bags[(++_oldest) % _size];
}
OpenCurrentBag( now, ++_current );
CheckIndexValid();
}
}
UsageThe UserCache.cs file contains an example of how the cache can be used: /// <summary>retrieve items by userid</summary>
public UserData FindByUserID( int userid )
{
return _findByUserID[userid];
}
/// <summary>retrieve items by username</summary>
public UserData FindByUserName( string username )
{
return _findByUserName[username];
}
/// <summary>constructor creates cache and multiple indexes</summary>
private UserCache() : base( 10000, TimeSpan.FromMinutes( 1 ),
TimeSpan.FromHours( 1 ), null )
{
_isValid = IsDataValid;
_findByUserID = AddIndex<int>( "UserID", delegate( UserData user )
{ return user.UserID; }, LoadFromUserID );
_findByUserName = AddIndex<string>( "UserName", delegate( UserData user )
{ return user.UserName; }, LoadFromUserName );
IsDataValid();
}
SummaryThis implementation of
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||