Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
4.7 KiB

  1. //===== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose: thread safe cache template class
  4. //
  5. //===========================================================================//
  6. // a thread-safe cache. Cache queries and updates may be performed on multiple threads at once in a
  7. // lock-free fashion. You must call the "FrameUpdate" function while no one is querying against
  8. // this cache.
  9. #include "tier1/utlintrusivelist.h"
  10. // in order to use this class, KEYTYPE must implement bool IsAcceptable( KEYTYPE const & ), and
  11. // RECORDTYPE must implement CreateCacheData( KEYTYPE const & )
  12. template< class RECORDTYPE, class KEYTYPE, class EXTRACREATEDATA, int nNumCacheLines, int nAssociativity = 4 > class CUtlTSCache
  13. {
  14. public:
  15. class CCacheEntry : public CAlignedNewDelete<16>
  16. {
  17. public:
  18. CCacheEntry *m_pNext;
  19. CCacheEntry *m_pNextPending;
  20. KEYTYPE m_Key;
  21. RECORDTYPE m_Data;
  22. };
  23. ~CUtlTSCache( void )
  24. {
  25. PerFrameUpdate(); // move pending to free
  26. for( CCacheEntry *pNode = m_pFreeList; pNode; )
  27. {
  28. CCacheEntry *pNext = pNode->m_pNext;
  29. if ( ! ( ( ( pNode >= m_pInitiallyAllocatedNodes ) &&
  30. ( pNode < m_pInitiallyAllocatedNodes + InitialAllocationSize() ) ) ) )
  31. {
  32. delete pNode;
  33. }
  34. pNode = pNext;
  35. }
  36. delete[] m_pInitiallyAllocatedNodes;
  37. }
  38. CUtlTSCache( void )
  39. {
  40. MEM_ALLOC_CREDIT_CLASS();
  41. memset( m_pCache, 0, sizeof( m_pCache ) );
  42. m_pInitiallyAllocatedNodes = new CCacheEntry[ InitialAllocationSize() ];
  43. m_pFreeList = NULL;
  44. for( int i = 0; i < InitialAllocationSize(); i++ )
  45. {
  46. IntrusiveList::AddToHead( m_pFreeList, m_pInitiallyAllocatedNodes + i );
  47. }
  48. m_pPendingFreeList = NULL;
  49. }
  50. void PerFrameUpdate( void )
  51. {
  52. for( CCacheEntry *pNode = m_pPendingFreeList; pNode; pNode = pNode->m_pNextPending )
  53. {
  54. IntrusiveList::AddToHead( m_pFreeList, pNode );
  55. }
  56. m_pPendingFreeList = NULL;
  57. }
  58. // Invalidate() is NOT thread-safe. If you need a thread-safe invalidate, you're going to need
  59. // to do something like store a generation count in the key.
  60. void Invalidate( void )
  61. {
  62. for( int i = 0; i < ARRAYSIZE( m_pCache ); i++ )
  63. {
  64. CCacheEntry *pNode = m_pCache[i];
  65. if ( pNode )
  66. {
  67. IntrusiveList::AddToHead( m_pFreeList, pNode );
  68. }
  69. m_pCache[i] = NULL;
  70. }
  71. }
  72. // lookup a value, maybe add one
  73. RECORDTYPE *Lookup( KEYTYPE const &key, EXTRACREATEDATA const &extraCreateData )
  74. {
  75. // first perform our hash function
  76. uint nHash = HashItem( key ) % nNumCacheLines;
  77. CCacheEntry **pCacheLine = m_pCache + nHash * nAssociativity;
  78. // now, see if we have an acceptable node
  79. CCacheEntry *pOldValue[nAssociativity];
  80. int nOffset = ( nAssociativity ) ? -1 : 0;
  81. for( int i = 0; i < nAssociativity; i++ )
  82. {
  83. pOldValue[i] = pCacheLine[i];
  84. if ( pOldValue[i] )
  85. {
  86. if ( key.IsAcceptable( pOldValue[i]->m_Key ) )
  87. {
  88. return &( pOldValue[i]->m_Data );
  89. }
  90. }
  91. else
  92. {
  93. nOffset = i; // replace empty lines first
  94. }
  95. }
  96. // no acceptable entry. We must generate and replace one. We will use a pseudo-random replacement scheme
  97. if ( ( nAssociativity > 1 ) && ( nOffset == -1 ) )
  98. {
  99. nOffset = ( m_nLineCounter++ ) % nAssociativity;
  100. }
  101. // get a node
  102. CCacheEntry *pNode = GetNode();
  103. pNode->m_Key = key;
  104. pNode->m_Data.CreateCacheData( key, extraCreateData );
  105. // we will look for a place to insert this. It is possible that we will find no good place and will just ditch it.
  106. for( int i = 0; i < nAssociativity; i++ )
  107. {
  108. // try to install it.
  109. if ( ThreadInterlockedAssignPointerIf( ( void * volatile * ) ( pCacheLine + nOffset ), pNode, pOldValue[nOffset] ) )
  110. {
  111. // put the old one on the pending free list
  112. if ( pOldValue[nOffset] )
  113. {
  114. IntrusiveList::AddToHeadByFieldTS( m_pPendingFreeList, pOldValue[nOffset], &CCacheEntry::m_pNextPending );
  115. }
  116. return &( pNode->m_Data ); // success!
  117. }
  118. nOffset++;
  119. if ( nOffset == nAssociativity )
  120. {
  121. nOffset = 0; // wrap around
  122. }
  123. }
  124. // we failed to install this node into the cache. we'll not bother
  125. IntrusiveList::AddToHeadByFieldTS( m_pPendingFreeList, pNode, &CCacheEntry::m_pNextPending );
  126. return &( pNode->m_Data );
  127. }
  128. protected:
  129. int InitialAllocationSize( void )
  130. {
  131. return nNumCacheLines * nAssociativity;
  132. }
  133. CCacheEntry *GetNode( void )
  134. {
  135. CCacheEntry *pNode = IntrusiveList::RemoveHeadTS( m_pFreeList );
  136. if ( ! pNode )
  137. {
  138. pNode = new CCacheEntry;
  139. }
  140. return pNode;
  141. }
  142. CInterlockedUInt m_nLineCounter; // for cycling through cache lines
  143. CCacheEntry *m_pCache[nNumCacheLines * nAssociativity];
  144. CCacheEntry *m_pFreeList; // nodes available for the cache
  145. CCacheEntry *m_pPendingFreeList; // nodes to be moved to the free list at end of frame
  146. CCacheEntry *m_pInitiallyAllocatedNodes;
  147. };