183 lines
4.7 KiB
C++
183 lines
4.7 KiB
C++
//===== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose: thread safe cache template class
|
|
//
|
|
//===========================================================================//
|
|
|
|
|
|
|
|
// a thread-safe cache. Cache queries and updates may be performed on multiple threads at once in a
|
|
// lock-free fashion. You must call the "FrameUpdate" function while no one is querying against
|
|
// this cache.
|
|
|
|
#include "tier1/utlintrusivelist.h"
|
|
|
|
// in order to use this class, KEYTYPE must implement bool IsAcceptable( KEYTYPE const & ), and
|
|
// RECORDTYPE must implement CreateCacheData( KEYTYPE const & )
|
|
template< class RECORDTYPE, class KEYTYPE, class EXTRACREATEDATA, int nNumCacheLines, int nAssociativity = 4 > class CUtlTSCache
|
|
{
|
|
|
|
public:
|
|
|
|
class CCacheEntry : public CAlignedNewDelete<16>
|
|
{
|
|
public:
|
|
CCacheEntry *m_pNext;
|
|
CCacheEntry *m_pNextPending;
|
|
KEYTYPE m_Key;
|
|
RECORDTYPE m_Data;
|
|
|
|
};
|
|
|
|
|
|
|
|
~CUtlTSCache( void )
|
|
{
|
|
PerFrameUpdate(); // move pending to free
|
|
for( CCacheEntry *pNode = m_pFreeList; pNode; )
|
|
{
|
|
CCacheEntry *pNext = pNode->m_pNext;
|
|
if ( ! ( ( ( pNode >= m_pInitiallyAllocatedNodes ) &&
|
|
( pNode < m_pInitiallyAllocatedNodes + InitialAllocationSize() ) ) ) )
|
|
{
|
|
delete pNode;
|
|
}
|
|
pNode = pNext;
|
|
}
|
|
delete[] m_pInitiallyAllocatedNodes;
|
|
}
|
|
|
|
CUtlTSCache( void )
|
|
{
|
|
MEM_ALLOC_CREDIT_CLASS();
|
|
memset( m_pCache, 0, sizeof( m_pCache ) );
|
|
m_pInitiallyAllocatedNodes = new CCacheEntry[ InitialAllocationSize() ];
|
|
m_pFreeList = NULL;
|
|
for( int i = 0; i < InitialAllocationSize(); i++ )
|
|
{
|
|
IntrusiveList::AddToHead( m_pFreeList, m_pInitiallyAllocatedNodes + i );
|
|
}
|
|
m_pPendingFreeList = NULL;
|
|
}
|
|
|
|
void PerFrameUpdate( void )
|
|
{
|
|
for( CCacheEntry *pNode = m_pPendingFreeList; pNode; pNode = pNode->m_pNextPending )
|
|
{
|
|
IntrusiveList::AddToHead( m_pFreeList, pNode );
|
|
}
|
|
m_pPendingFreeList = NULL;
|
|
}
|
|
|
|
// Invalidate() is NOT thread-safe. If you need a thread-safe invalidate, you're going to need
|
|
// to do something like store a generation count in the key.
|
|
void Invalidate( void )
|
|
{
|
|
for( int i = 0; i < ARRAYSIZE( m_pCache ); i++ )
|
|
{
|
|
CCacheEntry *pNode = m_pCache[i];
|
|
if ( pNode )
|
|
{
|
|
IntrusiveList::AddToHead( m_pFreeList, pNode );
|
|
}
|
|
m_pCache[i] = NULL;
|
|
}
|
|
}
|
|
|
|
// lookup a value, maybe add one
|
|
RECORDTYPE *Lookup( KEYTYPE const &key, EXTRACREATEDATA const &extraCreateData )
|
|
{
|
|
// first perform our hash function
|
|
uint nHash = HashItem( key ) % nNumCacheLines;
|
|
|
|
CCacheEntry **pCacheLine = m_pCache + nHash * nAssociativity;
|
|
|
|
// now, see if we have an acceptable node
|
|
CCacheEntry *pOldValue[nAssociativity];
|
|
int nOffset = ( nAssociativity ) ? -1 : 0;
|
|
for( int i = 0; i < nAssociativity; i++ )
|
|
{
|
|
pOldValue[i] = pCacheLine[i];
|
|
if ( pOldValue[i] )
|
|
{
|
|
if ( key.IsAcceptable( pOldValue[i]->m_Key ) )
|
|
{
|
|
return &( pOldValue[i]->m_Data );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nOffset = i; // replace empty lines first
|
|
}
|
|
|
|
}
|
|
// no acceptable entry. We must generate and replace one. We will use a pseudo-random replacement scheme
|
|
if ( ( nAssociativity > 1 ) && ( nOffset == -1 ) )
|
|
{
|
|
nOffset = ( m_nLineCounter++ ) % nAssociativity;
|
|
}
|
|
|
|
// get a node
|
|
CCacheEntry *pNode = GetNode();
|
|
pNode->m_Key = key;
|
|
pNode->m_Data.CreateCacheData( key, extraCreateData );
|
|
|
|
// we will look for a place to insert this. It is possible that we will find no good place and will just ditch it.
|
|
for( int i = 0; i < nAssociativity; i++ )
|
|
{
|
|
// try to install it.
|
|
if ( ThreadInterlockedAssignPointerIf( ( void * volatile * ) ( pCacheLine + nOffset ), pNode, pOldValue[nOffset] ) )
|
|
{
|
|
// put the old one on the pending free list
|
|
if ( pOldValue[nOffset] )
|
|
{
|
|
IntrusiveList::AddToHeadByFieldTS( m_pPendingFreeList, pOldValue[nOffset], &CCacheEntry::m_pNextPending );
|
|
}
|
|
return &( pNode->m_Data ); // success!
|
|
}
|
|
nOffset++;
|
|
if ( nOffset == nAssociativity )
|
|
{
|
|
nOffset = 0; // wrap around
|
|
}
|
|
}
|
|
// we failed to install this node into the cache. we'll not bother
|
|
IntrusiveList::AddToHeadByFieldTS( m_pPendingFreeList, pNode, &CCacheEntry::m_pNextPending );
|
|
return &( pNode->m_Data );
|
|
}
|
|
|
|
|
|
|
|
protected:
|
|
|
|
int InitialAllocationSize( void )
|
|
{
|
|
return nNumCacheLines * nAssociativity;
|
|
}
|
|
|
|
CCacheEntry *GetNode( void )
|
|
{
|
|
CCacheEntry *pNode = IntrusiveList::RemoveHeadTS( m_pFreeList );
|
|
if ( ! pNode )
|
|
{
|
|
pNode = new CCacheEntry;
|
|
}
|
|
return pNode;
|
|
}
|
|
|
|
|
|
CInterlockedUInt m_nLineCounter; // for cycling through cache lines
|
|
|
|
CCacheEntry *m_pCache[nNumCacheLines * nAssociativity];
|
|
|
|
CCacheEntry *m_pFreeList; // nodes available for the cache
|
|
CCacheEntry *m_pPendingFreeList; // nodes to be moved to the free list at end of frame
|
|
|
|
CCacheEntry *m_pInitiallyAllocatedNodes;
|
|
|
|
|
|
};
|
|
|
|
|
|
|