|
|
//===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//
// Thread-safe hash class
//===========================================================================//
#ifndef UTLTSHASH_H
#define UTLTSHASH_H
#ifdef _WIN32
#pragma once
#endif
#include <limits.h>
#include "tier0/threadtools.h"
#include "tier1/mempool.h"
#include "generichash.h"
//=============================================================================
//
// Threadsafe Hash
//
// Number of buckets must be a power of 2.
// Key must be intp sized (32-bits on x32, 64-bits on x64)
// Designed for a usage pattern where the data is semi-static, and there
// is a well-defined point where we are guaranteed no queries are occurring.
//
// Insertions are added into a thread-safe list, and when Commit() is called,
// the insertions are moved into a lock-free list
//
// Elements are never individually removed; clears must occur at a time
// where we and guaranteed no queries are occurring
//
typedef intp UtlTSHashHandle_t;
template < class T > abstract_class ITSHashConstructor { public: virtual void Construct( T* pElement ) = 0; };
template < class T > class CDefaultTSHashConstructor : public ITSHashConstructor< T > { public: virtual void Construct( T* pElement ) { ::Construct( pElement ); } };
template < int BUCKET_COUNT, class KEYTYPE = intp > class CUtlTSHashGenericHash { public: static int Hash( const KEYTYPE &key, int nBucketMask ) { int nHash = HashIntConventional( (intp)key ); if ( BUCKET_COUNT <= USHRT_MAX ) { nHash ^= ( nHash >> 16 ); } if ( BUCKET_COUNT <= UCHAR_MAX ) { nHash ^= ( nHash >> 8 ); } return ( nHash & nBucketMask ); }
static bool Compare( const KEYTYPE &lhs, const KEYTYPE &rhs ) { return lhs == rhs; } };
template < int BUCKET_COUNT, class KEYTYPE > class CUtlTSHashUseKeyHashMethod { public: static int Hash( const KEYTYPE &key, int nBucketMask ) { uint32 nHash = key.HashValue(); return ( nHash & nBucketMask ); }
static bool Compare( const KEYTYPE &lhs, const KEYTYPE &rhs ) { return lhs == rhs; } };
template< class T, int BUCKET_COUNT, class KEYTYPE = intp, class HashFuncs = CUtlTSHashGenericHash< BUCKET_COUNT, KEYTYPE >, int nAlignment = 0 > class CUtlTSHash { public: // Constructor/Deconstructor.
CUtlTSHash( int nAllocationCount ); ~CUtlTSHash();
// Invalid handle.
static UtlTSHashHandle_t InvalidHandle( void ) { return ( UtlTSHashHandle_t )0; }
// Retrieval. Super fast, is thread-safe
UtlTSHashHandle_t Find( KEYTYPE uiKey );
// Insertion ( find or add ).
UtlTSHashHandle_t Insert( KEYTYPE uiKey, const T &data, bool *pDidInsert = NULL ); UtlTSHashHandle_t Insert( KEYTYPE uiKey, ITSHashConstructor<T> *pConstructor, bool *pDidInsert = NULL );
// This insertion method assumes the element is not in the hash table, skips
UtlTSHashHandle_t FastInsert( KEYTYPE uiKey, const T &data ); UtlTSHashHandle_t FastInsert( KEYTYPE uiKey, ITSHashConstructor<T> *pConstructor );
// Commit recent insertions, making finding them faster.
// Only call when you're certain no threads are accessing the hash table
void Commit( );
// Removal. Only call when you're certain no threads are accessing the hash table
void FindAndRemove( KEYTYPE uiKey ); void Remove( UtlTSHashHandle_t hHash ) { FindAndRemove( GetID( hHash ) ); } void RemoveAll( void ); void Purge( void );
// Returns the number of elements in the hash table
int Count() const;
// Returns elements in the table
int GetElements( int nFirstElement, int nCount, UtlTSHashHandle_t *pHandles ) const;
// Element access
T &Element( UtlTSHashHandle_t hHash ); T const &Element( UtlTSHashHandle_t hHash ) const; T &operator[]( UtlTSHashHandle_t hHash ); T const &operator[]( UtlTSHashHandle_t hHash ) const; KEYTYPE GetID( UtlTSHashHandle_t hHash ) const;
// Convert element * to hashHandle
UtlTSHashHandle_t ElementPtrToHandle( T* pElement ) const;
private: // Templatized for memory tracking purposes
template < typename Data_t > struct HashFixedDataInternal_t { KEYTYPE m_uiKey; HashFixedDataInternal_t< Data_t >* m_pNext; Data_t m_Data; };
typedef HashFixedDataInternal_t<T> HashFixedData_t;
enum { BUCKET_MASK = BUCKET_COUNT - 1 };
struct HashBucket_t { HashFixedData_t *m_pFirst; HashFixedData_t *m_pFirstUncommitted; CThreadSpinRWLock m_AddLock; };
UtlTSHashHandle_t Find( KEYTYPE uiKey, HashFixedData_t *pFirstElement, HashFixedData_t *pLastElement ); UtlTSHashHandle_t InsertUncommitted( KEYTYPE uiKey, HashBucket_t &bucket ); CMemoryPoolMT m_EntryMemory; HashBucket_t m_aBuckets[BUCKET_COUNT]; bool m_bNeedsCommit;
#ifdef _DEBUG
CInterlockedInt m_ContentionCheck; #endif
};
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::CUtlTSHash( int nAllocationCount ) : m_EntryMemory( sizeof( HashFixedData_t ), nAllocationCount, CUtlMemoryPool::GROW_SLOW, MEM_ALLOC_CLASSNAME( HashFixedData_t ), nAlignment ) { #ifdef _DEBUG
m_ContentionCheck = 0; #endif
m_bNeedsCommit = false; for ( int i = 0; i < BUCKET_COUNT; i++ ) { HashBucket_t &bucket = m_aBuckets[ i ]; bucket.m_pFirst = NULL; bucket.m_pFirstUncommitted = NULL; } }
//-----------------------------------------------------------------------------
// Purpose: Deconstructor
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::~CUtlTSHash() { #ifdef _DEBUG
if ( m_ContentionCheck != 0 ) { DebuggerBreak(); } #endif
Purge(); }
//-----------------------------------------------------------------------------
// Purpose: Destroy dynamically allocated hash data.
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline void CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Purge( void ) { RemoveAll(); }
//-----------------------------------------------------------------------------
// Returns the number of elements in the hash table
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline int CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Count() const { return m_EntryMemory.Count(); }
//-----------------------------------------------------------------------------
// Returns elements in the table
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> int CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::GetElements( int nFirstElement, int nCount, UtlTSHashHandle_t *pHandles ) const { int nIndex = 0; for ( int i = 0; i < BUCKET_COUNT; i++ ) { const HashBucket_t &bucket = m_aBuckets[ i ]; bucket.m_AddLock.LockForRead( ); for ( HashFixedData_t *pElement = bucket.m_pFirstUncommitted; pElement; pElement = pElement->m_pNext ) { if ( --nFirstElement >= 0 ) continue;
pHandles[ nIndex++ ] = (UtlTSHashHandle_t)pElement; if ( nIndex >= nCount ) { bucket.m_AddLock.UnlockRead( ); return nIndex; } } bucket.m_AddLock.UnlockRead( ); } return nIndex; }
//-----------------------------------------------------------------------------
// Purpose: Insert data into the hash table given its key (KEYTYPE),
// without a check to see if the element already exists within the tree.
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::InsertUncommitted( KEYTYPE uiKey, HashBucket_t &bucket ) { m_bNeedsCommit = true; HashFixedData_t *pNewElement = static_cast< HashFixedData_t * >( m_EntryMemory.Alloc() ); pNewElement->m_pNext = bucket.m_pFirstUncommitted; bucket.m_pFirstUncommitted = pNewElement; pNewElement->m_uiKey = uiKey; return (UtlTSHashHandle_t)pNewElement; }
//-----------------------------------------------------------------------------
// Purpose: Insert data into the hash table given its key, with
// a check to see if the element already exists within the tree.
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Insert( KEYTYPE uiKey, const T &data, bool *pDidInsert ) { #ifdef _DEBUG
if ( m_ContentionCheck != 0 ) { DebuggerBreak(); } #endif
if ( pDidInsert ) { *pDidInsert = false; }
int iBucket = HashFuncs::Hash( uiKey, BUCKET_MASK ); HashBucket_t &bucket = m_aBuckets[ iBucket ];
// First try lock-free
UtlTSHashHandle_t h = Find( uiKey ); if ( h != InvalidHandle() ) return h;
// Now, try again, but only look in uncommitted elements
bucket.m_AddLock.LockForWrite( );
h = Find( uiKey, bucket.m_pFirstUncommitted, bucket.m_pFirst ); if ( h == InvalidHandle() ) { h = InsertUncommitted( uiKey, bucket ); CopyConstruct( &Element(h), data ); if ( pDidInsert ) { *pDidInsert = true; } }
bucket.m_AddLock.UnlockWrite( ); return h; }
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Insert( KEYTYPE uiKey, ITSHashConstructor<T> *pConstructor, bool *pDidInsert ) { #ifdef _DEBUG
if ( m_ContentionCheck != 0 ) { DebuggerBreak(); } #endif
if ( pDidInsert ) { *pDidInsert = false; }
// First try lock-free
UtlTSHashHandle_t h = Find( uiKey ); if ( h != InvalidHandle() ) return h;
// Now, try again, but only look in uncommitted elements
int iBucket = HashFuncs::Hash( uiKey, BUCKET_MASK ); HashBucket_t &bucket = m_aBuckets[ iBucket ]; bucket.m_AddLock.LockForWrite( );
h = Find( uiKey, bucket.m_pFirstUncommitted, bucket.m_pFirst ); if ( h == InvalidHandle() ) { // Useful if non-trivial work needs to happen to make data; don't want to
// do it and then have to undo it if it turns out we don't need to add it
h = InsertUncommitted( uiKey, bucket ); pConstructor->Construct( &Element(h) ); if ( pDidInsert ) { *pDidInsert = true; } }
bucket.m_AddLock.UnlockWrite( ); return h; }
//-----------------------------------------------------------------------------
// Purpose: Insert data into the hash table given its key
// without a check to see if the element already exists within the tree.
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::FastInsert( KEYTYPE uiKey, const T &data ) { #ifdef _DEBUG
if ( m_ContentionCheck != 0 ) { DebuggerBreak(); } #endif
int iBucket = HashFuncs::Hash( uiKey, BUCKET_MASK ); HashBucket_t &bucket = m_aBuckets[ iBucket ]; bucket.m_AddLock.LockForWrite( ); UtlTSHashHandle_t h = InsertUncommitted( uiKey, bucket ); CopyConstruct( &Element(h), data ); bucket.m_AddLock.UnlockWrite( ); return h; }
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::FastInsert( KEYTYPE uiKey, ITSHashConstructor<T> *pConstructor ) { #ifdef _DEBUG
if ( m_ContentionCheck != 0 ) { DebuggerBreak(); } #endif
int iBucket = HashFuncs::Hash( uiKey, BUCKET_MASK ); HashBucket_t &bucket = m_aBuckets[ iBucket ]; bucket.m_AddLock.LockForWrite( ); UtlTSHashHandle_t h = InsertUncommitted( uiKey, bucket ); pConstructor->Construct( &Element(h) ); bucket.m_AddLock.UnlockWrite( ); return h; }
//-----------------------------------------------------------------------------
// Purpose: Commits all uncommitted insertions
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline void CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Commit( ) { // FIXME: Is this legal? Want this to be lock-free
if ( !m_bNeedsCommit ) return;
// This must occur when no queries are occurring
#ifdef _DEBUG
m_ContentionCheck++; #endif
for ( int i = 0; i < BUCKET_COUNT; i++ ) { HashBucket_t &bucket = m_aBuckets[ i ]; bucket.m_AddLock.LockForRead( ); bucket.m_pFirst = bucket.m_pFirstUncommitted; bucket.m_AddLock.UnlockRead( ); }
m_bNeedsCommit = false;
#ifdef _DEBUG
m_ContentionCheck--; #endif
}
//-----------------------------------------------------------------------------
// Purpose: Remove a single element from the hash
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline void CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::FindAndRemove( KEYTYPE uiKey ) { if ( m_EntryMemory.Count() == 0 ) return;
// This must occur when no queries are occurring
#ifdef _DEBUG
m_ContentionCheck++; #endif
int iBucket = HashFuncs::Hash( uiKey, BUCKET_MASK ); HashBucket_t &bucket = m_aBuckets[ iBucket ]; bucket.m_AddLock.LockForWrite( );
HashFixedData_t *pPrev = NULL; for ( HashFixedData_t *pElement = bucket.m_pFirstUncommitted; pElement; pPrev = pElement, pElement = pElement->m_pNext ) { if ( !HashFuncs::Compare( pElement->m_uiKey, uiKey ) ) continue;
if ( pPrev ) { pPrev->m_pNext = pElement->m_pNext; } else { bucket.m_pFirstUncommitted = pElement->m_pNext; }
if ( bucket.m_pFirst == pElement ) { bucket.m_pFirst = bucket.m_pFirst->m_pNext; }
Destruct( &pElement->m_Data );
#ifdef _DEBUG
memset( pElement, 0xDD, sizeof(HashFixedData_t) ); #endif
m_EntryMemory.Free( pElement );
break; }
bucket.m_AddLock.UnlockWrite( );
#ifdef _DEBUG
m_ContentionCheck--; #endif
}
//-----------------------------------------------------------------------------
// Purpose: Remove all elements from the hash
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline void CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::RemoveAll( void ) { m_bNeedsCommit = false; if ( m_EntryMemory.Count() == 0 ) return;
// This must occur when no queries are occurring
#ifdef _DEBUG
m_ContentionCheck++; #endif
for ( int i = 0; i < BUCKET_COUNT; i++ ) { HashBucket_t &bucket = m_aBuckets[ i ];
bucket.m_AddLock.LockForWrite( );
for ( HashFixedData_t *pElement = bucket.m_pFirstUncommitted; pElement; pElement = pElement->m_pNext ) { Destruct( &pElement->m_Data ); }
bucket.m_pFirst = NULL; bucket.m_pFirstUncommitted = NULL; bucket.m_AddLock.UnlockWrite( ); }
m_EntryMemory.Clear();
#ifdef _DEBUG
m_ContentionCheck--; #endif
}
//-----------------------------------------------------------------------------
// Finds an element, but only in the committed elements
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Find( KEYTYPE uiKey, HashFixedData_t *pFirstElement, HashFixedData_t *pLastElement ) { #ifdef _DEBUG
if ( m_ContentionCheck != 0 ) { DebuggerBreak(); } #endif
for ( HashFixedData_t *pElement = pFirstElement; pElement != pLastElement; pElement = pElement->m_pNext ) { if ( HashFuncs::Compare( pElement->m_uiKey, uiKey ) ) return (UtlTSHashHandle_t)pElement; } return InvalidHandle(); }
//-----------------------------------------------------------------------------
// Finds an element, but only in the committed elements
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Find( KEYTYPE uiKey ) { int iBucket = HashFuncs::Hash( uiKey, BUCKET_MASK ); const HashBucket_t &bucket = m_aBuckets[iBucket]; UtlTSHashHandle_t h = Find( uiKey, bucket.m_pFirst, NULL ); if ( h != InvalidHandle() ) return h;
// Didn't find it in the fast ( committed ) list. Let's try the slow ( uncommitted ) one
bucket.m_AddLock.LockForRead( ); h = Find( uiKey, bucket.m_pFirstUncommitted, bucket.m_pFirst ); bucket.m_AddLock.UnlockRead( );
return h; }
//-----------------------------------------------------------------------------
// Purpose: Return data given a hash handle.
//-----------------------------------------------------------------------------
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline T &CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Element( UtlTSHashHandle_t hHash ) { return ((HashFixedData_t *)hHash)->m_Data; }
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline T const &CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::Element( UtlTSHashHandle_t hHash ) const { return ((HashFixedData_t *)hHash)->m_Data; }
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline T &CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::operator[]( UtlTSHashHandle_t hHash ) { return ((HashFixedData_t *)hHash)->m_Data; }
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline T const &CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::operator[]( UtlTSHashHandle_t hHash ) const { return ((HashFixedData_t *)hHash)->m_Data; }
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline KEYTYPE CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::GetID( UtlTSHashHandle_t hHash ) const { return ((HashFixedData_t *)hHash)->m_uiKey; }
// Convert element * to hashHandle
template<class T, int BUCKET_COUNT, class KEYTYPE, class HashFuncs, int nAlignment> inline UtlTSHashHandle_t CUtlTSHash<T,BUCKET_COUNT,KEYTYPE,HashFuncs,nAlignment>::ElementPtrToHandle( T* pElement ) const { Assert( pElement ); HashFixedData_t *pFixedData = (HashFixedData_t*)( (uint8*)pElement - offsetof( HashFixedData_t, m_Data ) ); Assert( m_EntryMemory.IsAllocationWithinPool( pFixedData ) ); return (UtlTSHashHandle_t)pFixedData; }
#endif // UTLTSHASH_H
|