2735 lines
91 KiB
C++
2735 lines
91 KiB
C++
/*++
|
||
|
||
|
||
Copyright (c) 1998-2001 Microsoft Corporation
|
||
|
||
Module Name :
|
||
LKRhash.h
|
||
|
||
Abstract:
|
||
Declares LKRhash: a fast, scalable, cache- and MP-friendly hash table
|
||
|
||
Deprecated: Use the version in %sdxroot%\inetsrv\iis\iisrearc\core\inc
|
||
instead.
|
||
|
||
Author:
|
||
Paul (Per-Ake) Larson, palarson@microsoft.com, July 1997
|
||
Murali R. Krishnan (MuraliK)
|
||
George V. Reilly (GeorgeRe) 06-Jan-1998
|
||
|
||
Environment:
|
||
Win32 - User Mode
|
||
|
||
Project:
|
||
Internet Information Server RunTime Library
|
||
|
||
Revision History:
|
||
10/01/1998 - Change name from LKhash to LKRhash
|
||
|
||
--*/
|
||
|
||
|
||
#define LKR_COMPACT_DELETE 1
|
||
#define LKR_NEWCODE 1
|
||
// #define LKR_FIND_FIRST 1
|
||
#define LKR_SUBTABLE 1
|
||
#define LKR_MASK 1
|
||
|
||
|
||
#ifndef __LKRHASH_H__
|
||
#define __LKRHASH_H__
|
||
|
||
//=====================================================================
|
||
// The class CLKRLinearHashTable defined in this file provides dynamic hash
|
||
// tables, i.e. tables that grow and shrink dynamically with
|
||
// the number of records in the table.
|
||
// The basic method used is linear hashing, as explained in:
|
||
//
|
||
// P.-A. Larson, Dynamic Hash Tables, Comm. of the ACM, 31, 4 (1988)
|
||
//
|
||
// This version has the following characteristics:
|
||
// - It is thread-safe and uses spin locks for synchronization.
|
||
// - It was designed to support very high rates of concurrent
|
||
// operations (inserts/deletes/lookups). It achieves this by
|
||
// (a) partitioning a CLKRHashTable into a collection of
|
||
// CLKRLinearHashTables to reduce contention on the global table lock.
|
||
// (b) minimizing the hold time on a table lock, preferring to lock
|
||
// down a bucket chain instead.
|
||
// - The design is L1 cache-conscious. See CNodeClump.
|
||
// - It is designed for sets varying in size from a dozen
|
||
// elements to several million.
|
||
//
|
||
// Main classes:
|
||
// CLKRLinearHashTable: thread-safe linear hash table
|
||
// CLKRHashTable: collection of CLKRLinearHashTables
|
||
// CTypedHashTable: typesafe wrapper for CLKRHashTable
|
||
//
|
||
//
|
||
// Paul Larson, palarson@microsoft.com, July 1997
|
||
// Original implementation with input from Murali R. Krishnan,
|
||
// muralik@microsoft.com.
|
||
//
|
||
// George V. Reilly, georgere@microsoft.com, Dec 1997-Jan 1998
|
||
// Massive cleanup and rewrite. Added templates.
|
||
//=====================================================================
|
||
|
||
|
||
// 1) Linear Hashing
|
||
// ------------------
|
||
//
|
||
// Linear hash tables grow and shrink dynamically with the number of
|
||
// records in the table. The growth or shrinkage is smooth: logically,
|
||
// one bucket at a time but physically in larger increments
|
||
// (64 buckets). An insertion (deletion) may cause an expansion
|
||
// (contraction) of the table. This causes relocation of a small number
|
||
// of records (at most one bucket worth). All operations (insert,
|
||
// delete, lookup) take constant expected time, regardless of the
|
||
// current size or the growth of the table.
|
||
//
|
||
// 2) LKR extensions to Linear hash table
|
||
// --------------------------------------
|
||
//
|
||
// Larson-Krishnan-Reilly extensions to Linear hash tables for multiprocessor
|
||
// scalability and improved cache performance.
|
||
//
|
||
// Traditional implementations of linear hash tables use one global lock
|
||
// to prevent interference between concurrent operations
|
||
// (insert/delete/lookup) on the table. The single lock easily becomes
|
||
// the bottleneck in SMP scenarios when multiple threads are used.
|
||
//
|
||
// Traditionally, a (hash) bucket is implemented as a chain of
|
||
// single-item nodes. Every operation results in chasing down a chain
|
||
// looking for an item. However, pointer chasing is very slow on modern
|
||
// systems because almost every jump results in a cache miss. L2 (or L3)
|
||
// cache misses are very expensive in missed CPU cycles and the cost is
|
||
// increasing (going to 100s of cycles in the future).
|
||
//
|
||
// LKR extensions offer
|
||
// 1) Partitioning (by hashing) of records among multiple subtables.
|
||
// Each subtable has locks but there is no global lock. Each
|
||
// subtable receives a much lower rate of operations, resulting in
|
||
// fewer conflicts.
|
||
//
|
||
// 2) Improved cache locality by grouping keys and their hash values
|
||
// into contigous chunks that fit exactly into one (or a few)
|
||
// cache lines.
|
||
//
|
||
// Specifically the implementation that exists here achieves this using
|
||
// the following techniques.
|
||
//
|
||
// Class CLKRHashTable is the top-level data structure that dynamically
|
||
// creates m_cSubTables linear hash tables. The CLKRLinearHashTables act as
|
||
// the subtables to which items and accesses are fanned out. A good
|
||
// hash function multiplexes requests uniformly to various subtables,
|
||
// thus minimizing traffic to any single subtable. The implemenation
|
||
// uses a home-grown version of bounded spinlocks, that is, a thread
|
||
// does not spin on a lock indefinitely, instead yielding after a
|
||
// predetermined number of loops.
|
||
//
|
||
// Each CLKRLinearHashTable consists of a CDirEntry pointing to segments
|
||
// each holding m_dwSegSize CBuckets. Each CBucket in turn consists of a
|
||
// chain of CNodeClumps. Each CNodeClump contains a group of
|
||
// NODES_PER_CLUMP hash values (aka hash keys or signatures) and
|
||
// pointers to the associated data items. Keeping the signatures
|
||
// together increases the cache locality in scans for lookup.
|
||
//
|
||
// Traditionally, people store a link-list element right inside the
|
||
// object that is hashed and use this link-list for the chaining of data
|
||
// blocks. However, keeping just the pointers to the data object and
|
||
// not chaining through them limits the need for bringing in the data
|
||
// object to the cache. We need to access the data object only if the
|
||
// hash values match. This limits the cache-thrashing behaviour
|
||
// exhibited by conventional implementations. It has the additional
|
||
// benefit that the objects themselves do not need to be modified
|
||
// in order to be collected in the hash table (i.e., it's non-invasive).
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// TODO
|
||
// * Debugging support for iisprobe and inetdbg?
|
||
// * Use auto_ptrs.
|
||
// * Provide ReplaceRecord and DeleteRecord methods on iterators.
|
||
// * Sloppy iterators
|
||
// * Provide implementations of the STL collection classes, map, set,
|
||
// multimap, and multiset.
|
||
// * Make exception safe.
|
||
//--------------------------------------------------------------------
|
||
|
||
|
||
#include <irtldbg.h>
|
||
#include <lstentry.h>
|
||
#include <hashfn.h>
|
||
#include <limits.h>
|
||
|
||
|
||
|
||
#ifdef __LKRHASH_NAMESPACE__
|
||
namespace LKRHash {
|
||
#endif // __LKRHASH_NAMESPACE__
|
||
|
||
enum LK_TABLESIZE {
|
||
LK_SMALL_TABLESIZE= 1, // < 200 elements
|
||
LK_MEDIUM_TABLESIZE= 2, // 200...10,000 elements
|
||
LK_LARGE_TABLESIZE= 3, // 10,000+ elements
|
||
};
|
||
|
||
// Default values for the hashtable constructors
|
||
enum {
|
||
LK_DFLT_MAXLOAD= 4, // Default upperbound on average chain length.
|
||
LK_DFLT_INITSIZE=LK_MEDIUM_TABLESIZE, // Default initial size of hash table
|
||
LK_DFLT_NUM_SUBTBLS= 0, // Use a heuristic to choose #subtables
|
||
};
|
||
|
||
// build fix hack
|
||
enum {
|
||
DFLT_LK_MAXLOAD= LK_DFLT_MAXLOAD,
|
||
DFLT_LK_INITSIZE= LK_DFLT_INITSIZE,
|
||
DFLT_LK_NUM_SUBTBLS= LK_DFLT_NUM_SUBTBLS,
|
||
};
|
||
|
||
//--------------------------------------------------------------------
|
||
// forward declarations
|
||
|
||
class IRTL_DLLEXP CLKRLinearHashTable;
|
||
|
||
class IRTL_DLLEXP CLKRHashTable;
|
||
|
||
template <class _Der, class _Rcd, class _Ky, class _HT, class _Iter>
|
||
class CTypedHashTable;
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Possible return codes from public member functions of
|
||
// CLKRLinearHashTable, CLKRHashTable, and CTypedHashTable
|
||
|
||
enum LK_RETCODE {
|
||
// severe errors < 0
|
||
LK_UNUSABLE = -99, // Table corrupted: all bets are off
|
||
LK_ALLOC_FAIL, // ran out of memory
|
||
LK_BAD_ITERATOR, // invalid iterator; e.g., points to another table
|
||
LK_BAD_RECORD, // invalid record; e.g., NULL for InsertRecord
|
||
LK_BAD_PARAMETERS, // invalid parameters; e.g., NULL fnptrs to ctor
|
||
LK_NOT_INITIALIZED, // LKRHashTableInit was not called
|
||
|
||
LK_SUCCESS = 0, // everything's okay
|
||
LK_KEY_EXISTS, // key already present for InsertRecord(no-overwrite)
|
||
LK_NO_SUCH_KEY, // key not found
|
||
LK_NO_MORE_ELEMENTS,// iterator exhausted
|
||
};
|
||
|
||
#define LK_SUCCEEDED(lkrc) ((lkrc) >= LK_SUCCESS)
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Return codes from PFnRecordPred.
|
||
|
||
enum LK_PREDICATE {
|
||
LKP_ABORT = 1, // Stop walking the table immediately
|
||
LKP_NO_ACTION = 2, // No action, just keep walking
|
||
LKP_PERFORM = 3, // Perform action and continue walking
|
||
LKP_PERFORM_STOP = 4, // Perform action, then stop
|
||
LKP_DELETE = 5, // Delete record and keep walking
|
||
LKP_DELETE_STOP = 6, // Delete record, then stop
|
||
};
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Return codes from PFnRecordAction.
|
||
|
||
enum LK_ACTION {
|
||
LKA_ABORT = 1, // Stop walking the table immediately
|
||
LKA_FAILED = 2, // Action failed; continue walking the table
|
||
LKA_SUCCEEDED = 3, // Action succeeded; continue walking the table
|
||
};
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Parameter to Apply and ApplyIf.
|
||
|
||
enum LK_LOCKTYPE {
|
||
LKL_READLOCK = 1, // Lock the table for reading (for constness)
|
||
LKL_WRITELOCK = 2, // Lock the table for writing
|
||
};
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Global table lock code. This is only used to measure how much a
|
||
// slowdown having a global lock on the CLKRHashTable causes. It is *never*
|
||
// used in production code.
|
||
|
||
|
||
// #define LKRHASH_GLOBAL_LOCK CCritSec
|
||
|
||
#ifdef LKRHASH_GLOBAL_LOCK
|
||
|
||
# define LKRHASH_GLOBAL_LOCK_DECLARATIONS() \
|
||
typedef LKRHASH_GLOBAL_LOCK GlobalLock; \
|
||
mutable GlobalLock m_lkGlobal;
|
||
|
||
# define LKRHASH_GLOBAL_READ_LOCK() m_lkGlobal.ReadLock()
|
||
# define LKRHASH_GLOBAL_WRITE_LOCK() m_lkGlobal.WriteLock()
|
||
# define LKRHASH_GLOBAL_READ_UNLOCK() m_lkGlobal.ReadUnlock()
|
||
# define LKRHASH_GLOBAL_WRITE_UNLOCK() m_lkGlobal.WriteUnlock()
|
||
|
||
#else // !LKRHASH_GLOBAL_LOCK
|
||
|
||
# define LKRHASH_GLOBAL_LOCK_DECLARATIONS()
|
||
|
||
// These ones will be optimized away by the compiler
|
||
# define LKRHASH_GLOBAL_READ_LOCK() ((void)0)
|
||
# define LKRHASH_GLOBAL_WRITE_LOCK() ((void)0)
|
||
# define LKRHASH_GLOBAL_READ_UNLOCK() ((void)0)
|
||
# define LKRHASH_GLOBAL_WRITE_UNLOCK() ((void)0)
|
||
|
||
#endif // !LKRHASH_GLOBAL_LOCK
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Statistical information returned by GetStatistics
|
||
//--------------------------------------------------------------------
|
||
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
|
||
class IRTL_DLLEXP CAveragedLockStats : public CLockStatistics
|
||
{
|
||
public:
|
||
int m_nItems;
|
||
|
||
CAveragedLockStats()
|
||
: m_nItems(1)
|
||
{}
|
||
};
|
||
|
||
#endif // LOCK_INSTRUMENTATION
|
||
|
||
|
||
|
||
class IRTL_DLLEXP CLKRHashTableStats
|
||
{
|
||
public:
|
||
int RecordCount; // number of records in the table
|
||
int TableSize; // table size in number of slots
|
||
int DirectorySize; // number of entries in directory
|
||
int LongestChain; // longest hash chain in the table
|
||
int EmptySlots; // number of unused hash slots
|
||
double SplitFactor; // fraction of buckets split
|
||
double AvgSearchLength; // average length of a successful search
|
||
double ExpSearchLength; // theoretically expected length
|
||
double AvgUSearchLength; // average length of an unsuccessful search
|
||
double ExpUSearchLength; // theoretically expected length
|
||
int NodeClumpSize; // number of slots in a node clump
|
||
int CBucketSize; // sizeof(CBucket)
|
||
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
CAveragedLockStats m_alsTable; // stats for table lock
|
||
CAveragedLockStats m_alsBucketsAvg; // avg of stats for bucket locks
|
||
CGlobalLockStatistics m_gls; // global statistics for all locks
|
||
#endif // LOCK_INSTRUMENTATION
|
||
|
||
enum {
|
||
MAX_BUCKETS = 40,
|
||
};
|
||
|
||
// histogram of bucket lengths
|
||
LONG m_aBucketLenHistogram[MAX_BUCKETS];
|
||
|
||
CLKRHashTableStats()
|
||
: RecordCount(0),
|
||
TableSize(0),
|
||
DirectorySize(0),
|
||
LongestChain(0),
|
||
EmptySlots(0),
|
||
SplitFactor(0.0),
|
||
AvgSearchLength(0.0),
|
||
ExpSearchLength(0.0),
|
||
AvgUSearchLength(0.0),
|
||
ExpUSearchLength(0.0),
|
||
NodeClumpSize(1),
|
||
CBucketSize(0)
|
||
{
|
||
for (int i = MAX_BUCKETS; --i >= 0; )
|
||
m_aBucketLenHistogram[i] = 0;
|
||
}
|
||
|
||
static const LONG*
|
||
BucketSizes()
|
||
{
|
||
static const LONG s_aBucketSizes[MAX_BUCKETS] = {
|
||
1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||
20, 21, 22, 23, 24, 25, 30, 40, 50, 60,
|
||
70, 80, 90, 100, 200, 500, 1000,10000, 100000, LONG_MAX,
|
||
};
|
||
|
||
return s_aBucketSizes;
|
||
}
|
||
|
||
static LONG
|
||
BucketSize(
|
||
LONG nBucketIndex)
|
||
{
|
||
IRTLASSERT(0 <= nBucketIndex && nBucketIndex < MAX_BUCKETS);
|
||
return BucketSizes()[nBucketIndex];
|
||
}
|
||
|
||
static LONG
|
||
BucketIndex(
|
||
LONG nBucketLength)
|
||
{
|
||
const LONG* palBucketSizes = BucketSizes();
|
||
LONG i = 0;
|
||
while (palBucketSizes[i] < nBucketLength)
|
||
++i;
|
||
if (i == MAX_BUCKETS || palBucketSizes[i] > nBucketLength)
|
||
--i;
|
||
IRTLASSERT(0 <= i && i < MAX_BUCKETS);
|
||
return i;
|
||
}
|
||
};
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// CLKRLinearHashTable deals with void* records. These typedefs
|
||
// provide prototypes for functions that manipulate instances of
|
||
// those records. CTypedHashTable and CStringTestHashTable (below) show a
|
||
// way to encapsulate these in typesafe wrappers.
|
||
//--------------------------------------------------------------------
|
||
|
||
// Given a record, return its key. Assumes that the key is embedded in
|
||
// the record, or at least somehow derivable from the record. For
|
||
// completely unrelated keys & values, a wrapper class should use
|
||
// something like STL's pair<key,value> template to aggregate them
|
||
// into a record.
|
||
typedef const DWORD_PTR (WINAPI *PFnExtractKey) (const void* pvRecord);
|
||
|
||
// Given a key, return its hash signature. The hashing functions in
|
||
// hashfn.h (or something that builds upon them) are suggested.
|
||
typedef DWORD (WINAPI *PFnCalcKeyHash) (const DWORD_PTR pnKey);
|
||
|
||
// Compare two keys for equality; e.g., _stricmp, memcmp, operator==
|
||
typedef bool (WINAPI *PFnEqualKeys) (const DWORD_PTR pnKey1,
|
||
const DWORD_PTR pnKey2);
|
||
|
||
// Increment the reference count of a record before returning it from
|
||
// FindKey. It's necessary to do it in FindKey itself while the bucket
|
||
// is still locked, rather than one of the wrappers, to avoid race
|
||
// conditions. Similarly, the reference count is incremented in
|
||
// InsertRecord and decremented in DeleteKey. Finally, if an old record
|
||
// is overwritten in InsertRecord, its reference count is decremented.
|
||
//
|
||
// It's up to you to decrement the reference count when you're finished
|
||
// with it after retrieving it via FindKey and to determine the
|
||
// semantics of what this means. The hashtable itself has no notion of
|
||
// reference counts; this is merely to help with the lifetime management
|
||
// of the record objects.
|
||
typedef void (WINAPI *PFnAddRefRecord)(const void* pvRecord, int nIncr);
|
||
|
||
// ApplyIf() and DeleteIf(): Does the record match the predicate?
|
||
typedef LK_PREDICATE (WINAPI *PFnRecordPred) (const void* pvRecord,
|
||
void* pvState);
|
||
|
||
// Apply() et al: Perform action on record.
|
||
typedef LK_ACTION (WINAPI *PFnRecordAction)(const void* pvRecord,
|
||
void* pvState);
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// Custom memory allocators
|
||
//--------------------------------------------------------------------
|
||
|
||
|
||
#define LKRHASH_ACACHE 1
|
||
// #define LKRHASH_MANODEL 1
|
||
// #define LKRHASH_MADEL 1
|
||
|
||
// #define LKRHASH_MEM_DEFAULT_ALIGN 32
|
||
|
||
#ifndef LKRHASH_MEM_DEFAULT_ALIGN
|
||
# define LKRHASH_MEM_DEFAULT_ALIGN 8
|
||
#endif
|
||
|
||
#if defined(LKRHASH_ACACHE)
|
||
|
||
# include <acache.hxx>
|
||
typedef ALLOC_CACHE_HANDLER CLKRhashAllocator;
|
||
# define LKRHASH_ALLOCATOR_NEW(C, N) \
|
||
const ALLOC_CACHE_CONFIGURATION acc = { 1, N, sizeof(C) }; \
|
||
C::sm_palloc = new ALLOC_CACHE_HANDLER("IISRTL:" #C, &acc);
|
||
|
||
#elif defined(LKRHASH_MANODEL)
|
||
|
||
# include <manodel.hxx>
|
||
typedef MEMORY_ALLOC_NO_DELETE CLKRhashAllocator;
|
||
# define LKRHASH_ALLOCATOR_NEW(C, N) \
|
||
C::sm_palloc = new MEMORY_ALLOC_NO_DELETE(sizeof(C), \
|
||
LKRHASH_MEM_DEFAULT_ALIGN);
|
||
|
||
#elif defined(LKRHASH_MADEL)
|
||
|
||
# include <madel.hxx>
|
||
typedef MEMORY_ALLOC_DELETE CLKRhashAllocator;
|
||
# define LKRHASH_ALLOCATOR_NEW(C, N) \
|
||
C::sm_palloc = new MEMORY_ALLOC_DELETE(sizeof(C), \
|
||
LKRHASH_MEM_DEFAULT_ALIGN, N);
|
||
|
||
#else // no custom allocator
|
||
|
||
# undef LKRHASH_ALLOCATOR_NEW
|
||
|
||
#endif // no custom allocator
|
||
|
||
|
||
|
||
// Used to initialize and destroy custom allocators
|
||
bool LKRHashTableInit();
|
||
void LKRHashTableUninit();
|
||
|
||
|
||
#ifdef LKRHASH_ALLOCATOR_NEW
|
||
|
||
// placed inline in the declaration of class C
|
||
# define LKRHASH_ALLOCATOR_DEFINITIONS(C) \
|
||
protected: \
|
||
static CLKRhashAllocator* sm_palloc; \
|
||
friend bool LKRHashTableInit(); \
|
||
friend void LKRHashTableUninit(); \
|
||
public: \
|
||
static void* operator new(size_t s) \
|
||
{ \
|
||
IRTLASSERT(s == sizeof(C)); \
|
||
IRTLASSERT(sm_palloc != NULL); \
|
||
return sm_palloc->Alloc(); \
|
||
} \
|
||
static void operator delete(void* pv) \
|
||
{ \
|
||
IRTLASSERT(pv != NULL); \
|
||
IRTLASSERT(sm_palloc != NULL); \
|
||
sm_palloc->Free(pv); \
|
||
}
|
||
|
||
|
||
// used in LKRHashTableInit()
|
||
# define LKRHASH_ALLOCATOR_INIT(C, N, f) \
|
||
{ \
|
||
if (f) \
|
||
{ \
|
||
IRTLASSERT(C::sm_palloc == NULL); \
|
||
LKRHASH_ALLOCATOR_NEW(C, N); \
|
||
f = (C::sm_palloc != NULL); \
|
||
} \
|
||
}
|
||
|
||
|
||
// used in LKRHashTableUninit()
|
||
# define LKRHASH_ALLOCATOR_UNINIT(C) \
|
||
{ \
|
||
if (C::sm_palloc != NULL) \
|
||
{ \
|
||
delete C::sm_palloc; \
|
||
C::sm_palloc = NULL; \
|
||
} \
|
||
}
|
||
|
||
|
||
#else // !LKRHASH_ALLOCATOR_NEW
|
||
|
||
# define LKRHASH_ALLOCATOR_DEFINITIONS(C)
|
||
# define LKRHASH_ALLOCATOR_INIT(C, N, f)
|
||
# define LKRHASH_ALLOCATOR_UNINIT(C)
|
||
|
||
#endif // !LKRHASH_ALLOCATOR_NEW
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// CLKRLinearHashTable
|
||
//
|
||
// A thread-safe linear hash table.
|
||
//--------------------------------------------------------------------
|
||
|
||
class IRTL_DLLEXP CLKRLinearHashTable
|
||
{
|
||
public:
|
||
// typedef CSmallSpinLock TableLock;
|
||
// typedef CFakeLock TableLock;
|
||
// typedef CSpinLock TableLock;
|
||
// typedef CSpinLock2 TableLock;
|
||
// typedef CSpinLock3 TableLock;
|
||
// typedef CShareLock TableLock;
|
||
// typedef CReaderWriterLock TableLock;
|
||
// typedef CReaderWriterLock2 TableLock;
|
||
typedef CReaderWriterLock3 TableLock;
|
||
|
||
typedef CSmallSpinLock BucketLock;
|
||
// typedef CFakeLock BucketLock;
|
||
// typedef CSpinLock BucketLock;
|
||
// typedef CSpinLock2 BucketLock;
|
||
// typedef CSpinLock3 BucketLock;
|
||
// typedef CShareLock BucketLock;
|
||
// typedef CReaderWriterLock BucketLock;
|
||
// typedef CReaderWriterLock2 BucketLock;
|
||
// typedef CReaderWriterLock3 BucketLock;
|
||
|
||
class CIterator;
|
||
friend class CLKRLinearHashTable::CIterator;
|
||
|
||
private:
|
||
class CNodeClump;
|
||
friend class CLKRLinearHashTable::CNodeClump;
|
||
|
||
friend class CLKRHashTable;
|
||
|
||
#ifdef LKRHASH_ALLOCATOR_NEW
|
||
friend bool LKRHashTableInit();
|
||
friend void LKRHashTableUninit();
|
||
#endif // LKRHASH_ALLOCATOR_NEW
|
||
|
||
#ifdef LKRHASH_INSTRUMENTATION
|
||
// TODO
|
||
#endif // LKRHASH_INSTRUMENTATION
|
||
|
||
enum {
|
||
// Given M = A % B, A and B unsigned 32-bit integers greater than zero,
|
||
// there are no values of A or B which yield M = 2^32-1. Why? Because
|
||
// M must be less than B.
|
||
HASH_INVALID_SIGNATURE = ULONG_MAX,
|
||
};
|
||
|
||
|
||
// Class for nodes on a bucket chain. Instead of a node containing
|
||
// one (signature, record-pointer, next-tuple-pointer) tuple, it
|
||
// contains _N_ such tuples. (N-1 next-tuple-pointers are omitted.)
|
||
// This improves locality of reference greatly; i.e., it's L1
|
||
// cache-friendly. It also reduces memory fragmentation and memory
|
||
// allocator overhead. It does complicate the chain traversal code
|
||
// slightly, admittedly.
|
||
//
|
||
// This theory is beautiful. In practice, however, CNodeClumps
|
||
// are *not* perfectly aligned on 32-byte boundaries by the memory
|
||
// allocators. Experimental results indicate that we get a 2-3%
|
||
// speed improvement by using 32-byte-aligned blocks, but this must
|
||
// be considered against the average of 16 bytes wasted per block.
|
||
|
||
class CNodeClump
|
||
{
|
||
public:
|
||
// Record slots per chunk - set so a chunk matches (one or
|
||
// two) cache lines. 2 ==> 28 bytes, 6 ==> 60 bytes
|
||
// Note: the default max load factor is 4.0, which implies that
|
||
// there will seldom be more than one node clump in a chain.
|
||
enum {
|
||
BUCKET_BYTE_SIZE = 64,
|
||
BUCKET_OVERHEAD = sizeof(BucketLock) + sizeof(CNodeClump*),
|
||
NODE_SIZE = sizeof(const void*) + sizeof(DWORD),
|
||
NODES_PER_CLUMP = (BUCKET_BYTE_SIZE - BUCKET_OVERHEAD) / NODE_SIZE
|
||
};
|
||
|
||
DWORD m_dwKeySigs[NODES_PER_CLUMP]; // hash values computed from keys
|
||
CNodeClump* m_pncNext; // next node clump on the chain
|
||
const void* m_pvNode[NODES_PER_CLUMP];// pointers to records
|
||
|
||
CNodeClump()
|
||
{
|
||
Clear();
|
||
}
|
||
|
||
void
|
||
Clear()
|
||
{
|
||
m_pncNext = NULL; // no dangling pointers
|
||
for (DWORD i = 0; i < NODES_PER_CLUMP; ++i)
|
||
{
|
||
m_dwKeySigs[i] = HASH_INVALID_SIGNATURE;
|
||
m_pvNode[i] = NULL;
|
||
}
|
||
}
|
||
|
||
bool
|
||
InvalidSignature(
|
||
int i) const
|
||
{
|
||
IRTLASSERT(0 <= i && i < NODES_PER_CLUMP);
|
||
return (m_dwKeySigs[i] == HASH_INVALID_SIGNATURE);
|
||
}
|
||
|
||
bool
|
||
IsEmptyNode(
|
||
int i) const
|
||
{
|
||
IRTLASSERT(0 <= i && i < NODES_PER_CLUMP);
|
||
return (m_pvNode[i] == NULL);
|
||
}
|
||
|
||
bool
|
||
IsLastClump() const
|
||
{
|
||
return (m_pncNext == NULL);
|
||
}
|
||
|
||
#ifdef _DEBUG
|
||
// Don't want overhead of calls to dtor in retail build
|
||
~CNodeClump()
|
||
{
|
||
IRTLASSERT(IsLastClump()); // no dangling pointers
|
||
for (DWORD i = 0; i < NODES_PER_CLUMP; ++i)
|
||
IRTLASSERT(InvalidSignature(i) && IsEmptyNode(i));
|
||
}
|
||
#endif // _DEBUG
|
||
|
||
LKRHASH_ALLOCATOR_DEFINITIONS(CNodeClump);
|
||
};
|
||
|
||
|
||
// Class for bucket chains of the hash table. Note that the first
|
||
// nodeclump is actually included in the bucket and not dynamically
|
||
// allocated, which increases space requirements slightly but does
|
||
// improve performance.
|
||
class CBucket
|
||
{
|
||
private:
|
||
mutable BucketLock m_Lock; // lock protecting this bucket
|
||
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
static LONG sm_cBuckets;
|
||
|
||
static const char*
|
||
_LockName()
|
||
{
|
||
LONG l = ++sm_cBuckets;
|
||
// possible race condition but we don't care, as this is never
|
||
// used in production code
|
||
static char s_szName[CLockStatistics::L_NAMELEN];
|
||
wsprintf(s_szName, "B%06x", 0xFFFFFF & l);
|
||
return s_szName;
|
||
}
|
||
#endif // LOCK_INSTRUMENTATION
|
||
|
||
public:
|
||
CNodeClump m_ncFirst; // first CNodeClump of this bucket
|
||
|
||
#if defined(LOCK_INSTRUMENTATION) || defined(_DEBUG)
|
||
CBucket()
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
: m_Lock(_LockName())
|
||
#endif // LOCK_INSTRUMENTATION
|
||
{
|
||
#ifdef _DEBUG
|
||
LOCK_LOCKTYPE lt = BucketLock::LockType();
|
||
if (lt == LOCK_SPINLOCK || lt == LOCK_FAKELOCK)
|
||
IRTLASSERT(sizeof(*this) <= 64);
|
||
#endif _DEBUG
|
||
}
|
||
#endif // LOCK_INSTRUMENTATION || _DEBUG
|
||
|
||
void WriteLock() { m_Lock.WriteLock(); }
|
||
void ReadLock() const { m_Lock.ReadLock(); }
|
||
void WriteUnlock() const { m_Lock.WriteUnlock(); }
|
||
void ReadUnlock() const { m_Lock.ReadUnlock(); }
|
||
bool IsWriteLocked() const { return m_Lock.IsWriteLocked(); }
|
||
bool IsReadLocked() const { return m_Lock.IsReadLocked(); }
|
||
bool IsWriteUnlocked() const { return m_Lock.IsWriteUnlocked(); }
|
||
bool IsReadUnlocked() const { return m_Lock.IsReadUnlocked(); }
|
||
void SetSpinCount(WORD wSpins) { m_Lock.SetSpinCount(wSpins); }
|
||
WORD GetSpinCount() const { return m_Lock.GetSpinCount(); }
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
CLockStatistics LockStats() const {return m_Lock.Statistics();}
|
||
#endif // LOCK_INSTRUMENTATION
|
||
};
|
||
|
||
|
||
// The hash table space is divided into fixed-size segments (arrays of
|
||
// CBuckets) and physically grows/shrinks one segment at a time.
|
||
|
||
// We provide small, medium, and large segments to better tune the
|
||
// overall memory requirements of the hash table according to the
|
||
// expected usage of an instance.
|
||
|
||
class CSegment
|
||
{
|
||
public:
|
||
virtual ~CSegment() {}; // link fails if this is pure virtual
|
||
virtual DWORD Bits() const = 0;
|
||
virtual DWORD Size() const = 0;
|
||
virtual DWORD Mask() const = 0;
|
||
virtual DWORD InitSize() const = 0;
|
||
virtual CBucket& Slot(DWORD i) = 0;
|
||
};
|
||
|
||
|
||
// Small-sized segments contain 2^3 = 8 buckets => ~0.5Kb
|
||
class CSmallSegment : public CSegment
|
||
{
|
||
public:
|
||
// Maximum table size equals MAX_DIRSIZE * SEGSIZE buckets.
|
||
enum {
|
||
SEGBITS = 3,// number of bits extracted from a hash
|
||
// address for offset within a segment
|
||
SEGSIZE = (1<<SEGBITS),// segment size
|
||
SEGMASK = (SEGSIZE-1), // mask used for extracting offset bit
|
||
INITSIZE = 1 * SEGSIZE, // #segments to allocate initially
|
||
};
|
||
|
||
private:
|
||
CBucket m_bktSlots[SEGSIZE];
|
||
|
||
public:
|
||
virtual ~CSmallSegment() {}
|
||
virtual DWORD Bits() const { return SEGBITS; }
|
||
virtual DWORD Size() const { return SEGSIZE; }
|
||
virtual DWORD Mask() const { return SEGMASK; }
|
||
virtual DWORD InitSize() const { return INITSIZE;}
|
||
virtual CBucket& Slot(DWORD i)
|
||
{ IRTLASSERT(i < SEGSIZE); return m_bktSlots[i]; }
|
||
|
||
#ifdef _DEBUG
|
||
CSmallSegment()
|
||
{
|
||
IRTLASSERT(((DWORD_PTR)this & (LKRHASH_MEM_DEFAULT_ALIGN-1)) == 0);
|
||
IRTLASSERT(sizeof(*this)
|
||
== SEGSIZE * sizeof(CBucket) + sizeof(void*));
|
||
}
|
||
#endif // _DEBUG
|
||
|
||
LKRHASH_ALLOCATOR_DEFINITIONS(CSmallSegment);
|
||
};
|
||
|
||
|
||
// Medium-sized segments contain 2^6 = 64 buckets => ~4Kb
|
||
class CMediumSegment : public CSegment
|
||
{
|
||
public:
|
||
enum {
|
||
SEGBITS = 6,
|
||
SEGSIZE = (1<<SEGBITS),
|
||
SEGMASK = (SEGSIZE-1),
|
||
INITSIZE = 2 * SEGSIZE,
|
||
};
|
||
|
||
private:
|
||
CBucket m_bktSlots[SEGSIZE];
|
||
|
||
public:
|
||
virtual ~CMediumSegment() {}
|
||
virtual DWORD Bits() const { return SEGBITS; }
|
||
virtual DWORD Size() const { return SEGSIZE; }
|
||
virtual DWORD Mask() const { return SEGMASK; }
|
||
virtual DWORD InitSize() const { return INITSIZE;}
|
||
virtual CBucket& Slot(DWORD i)
|
||
{ IRTLASSERT(i < SEGSIZE); return m_bktSlots[i]; }
|
||
|
||
#ifdef _DEBUG
|
||
CMediumSegment()
|
||
{
|
||
IRTLASSERT(((DWORD_PTR)this & (LKRHASH_MEM_DEFAULT_ALIGN-1)) == 0);
|
||
IRTLASSERT(sizeof(*this)
|
||
== SEGSIZE * sizeof(CBucket) + sizeof(void*));
|
||
}
|
||
#endif // _DEBUG
|
||
|
||
LKRHASH_ALLOCATOR_DEFINITIONS(CMediumSegment);
|
||
};
|
||
|
||
// Large-sized segments contain 2^9 = 512 buckets => ~32Kb
|
||
class CLargeSegment : public CSegment
|
||
{
|
||
public:
|
||
enum {
|
||
SEGBITS = 9,
|
||
SEGSIZE = (1<<SEGBITS),
|
||
SEGMASK = (SEGSIZE-1),
|
||
INITSIZE = 4 * SEGSIZE,
|
||
};
|
||
|
||
private:
|
||
CBucket m_bktSlots[SEGSIZE];
|
||
|
||
public:
|
||
virtual ~CLargeSegment() {}
|
||
virtual DWORD Bits() const { return SEGBITS; }
|
||
virtual DWORD Size() const { return SEGSIZE; }
|
||
virtual DWORD Mask() const { return SEGMASK; }
|
||
virtual DWORD InitSize() const { return INITSIZE;}
|
||
virtual CBucket& Slot(DWORD i)
|
||
{ IRTLASSERT(i < SEGSIZE); return m_bktSlots[i]; }
|
||
|
||
|
||
#ifdef _DEBUG
|
||
CLargeSegment()
|
||
{
|
||
IRTLASSERT(((DWORD_PTR)this & (LKRHASH_MEM_DEFAULT_ALIGN-1)) == 0);
|
||
IRTLASSERT(sizeof(*this)
|
||
== SEGSIZE * sizeof(CBucket) + sizeof(void*));
|
||
}
|
||
#endif // _DEBUG
|
||
|
||
LKRHASH_ALLOCATOR_DEFINITIONS(CLargeSegment);
|
||
};
|
||
|
||
|
||
// A directory keeps track of the segments comprising the hash table.
|
||
// The directory is just a variable-sized array of pointers to
|
||
// segments (CDirEntrys).
|
||
class CDirEntry
|
||
{
|
||
public:
|
||
// MIN_DIRSIZE and MAX_DIRSIZE can be changed independently
|
||
// of anything else. Should be powers of two.
|
||
enum {
|
||
MIN_DIRSIZE = (1<<3), // minimum directory size
|
||
MAX_DIRSIZE = (1<<20), // maximum directory size
|
||
};
|
||
|
||
CSegment* m_pseg;
|
||
|
||
CDirEntry()
|
||
: m_pseg(NULL)
|
||
{}
|
||
|
||
~CDirEntry()
|
||
{ delete m_pseg; }
|
||
};
|
||
|
||
public:
|
||
|
||
// aliases for convenience
|
||
enum {
|
||
NODES_PER_CLUMP = CNodeClump::NODES_PER_CLUMP,
|
||
MIN_DIRSIZE = CDirEntry::MIN_DIRSIZE,
|
||
MAX_DIRSIZE = CDirEntry::MAX_DIRSIZE,
|
||
NAME_SIZE = 16,
|
||
};
|
||
|
||
|
||
private:
|
||
|
||
// Miscellaneous helper functions
|
||
|
||
// Convert a hash signature to a bucket address
|
||
inline DWORD _BucketAddress(DWORD dwSignature) const
|
||
{
|
||
DWORD dwBktAddr = _H0(dwSignature);
|
||
// Has this bucket been split already?
|
||
if (dwBktAddr < m_iExpansionIdx)
|
||
dwBktAddr = _H1(dwSignature);
|
||
IRTLASSERT(dwBktAddr < m_cActiveBuckets);
|
||
IRTLASSERT(dwBktAddr < (m_cDirSegs << m_dwSegBits));
|
||
return dwBktAddr;
|
||
}
|
||
|
||
// See the Linear Hashing paper
|
||
static DWORD _H0(DWORD dwSignature, DWORD dwBktAddrMask)
|
||
{ return dwSignature & dwBktAddrMask; }
|
||
|
||
DWORD _H0(DWORD dwSignature) const
|
||
{ return _H0(dwSignature, m_dwBktAddrMask); }
|
||
|
||
// See the Linear Hashing paper. Preserves one bit more than _H0.
|
||
static DWORD _H1(DWORD dwSignature, DWORD dwBktAddrMask)
|
||
{ return dwSignature & ((dwBktAddrMask << 1) | 1); }
|
||
|
||
DWORD _H1(DWORD dwSignature) const
|
||
#ifdef LKR_MASK
|
||
{ return _H0(dwSignature, m_dwBktAddrMask1); }
|
||
#else // !LKR_MASK
|
||
{ return _H1(dwSignature, m_dwBktAddrMask); }
|
||
#endif // !LKR_MASK
|
||
|
||
// In which segment within the directory does the bucketaddress lie?
|
||
// (Return type must be lvalue so that it can be assigned to.)
|
||
CSegment*& _Segment(DWORD dwBucketAddr) const
|
||
{
|
||
const DWORD iSeg = dwBucketAddr >> m_dwSegBits;
|
||
IRTLASSERT(m_paDirSegs != NULL && iSeg < m_cDirSegs);
|
||
return m_paDirSegs[iSeg].m_pseg;
|
||
}
|
||
|
||
// Offset within the segment of the bucketaddress
|
||
DWORD _SegIndex(DWORD dwBucketAddr) const
|
||
{ return (dwBucketAddr & m_dwSegMask); }
|
||
|
||
// Convert a bucketaddress to a CBucket*
|
||
inline CBucket* _Bucket(DWORD dwBucketAddr) const
|
||
{
|
||
IRTLASSERT(dwBucketAddr < m_cActiveBuckets);
|
||
CSegment* const pseg = _Segment(dwBucketAddr);
|
||
IRTLASSERT(pseg != NULL);
|
||
return &(pseg->Slot(_SegIndex(dwBucketAddr)));
|
||
}
|
||
|
||
// Extract the key from a record
|
||
const DWORD_PTR _ExtractKey(const void* pvRecord) const
|
||
{
|
||
IRTLASSERT(pvRecord != NULL);
|
||
IRTLASSERT(m_pfnExtractKey != NULL);
|
||
#ifdef LKR_SUBTABLE
|
||
return (*m_pfnExtractKey)(pvRecord);
|
||
#else
|
||
return (pvRecord != NULL) ? (*m_pfnExtractKey)(pvRecord) : NULL;
|
||
#endif
|
||
}
|
||
|
||
// Hash a key
|
||
DWORD _CalcKeyHash(const DWORD_PTR pnKey) const
|
||
{
|
||
// Note pnKey==0 is acceptable, as the real key type could be an int
|
||
IRTLASSERT(m_pfnCalcKeyHash != NULL);
|
||
DWORD dwHash = (*m_pfnCalcKeyHash)(pnKey);
|
||
// We forcibly scramble the result to help ensure a better distribution
|
||
dwHash = HashScramble(dwHash);
|
||
IRTLASSERT(dwHash != HASH_INVALID_SIGNATURE);
|
||
return dwHash;
|
||
}
|
||
|
||
// Compare two keys for equality
|
||
bool _EqualKeys(const DWORD_PTR pnKey1, const DWORD_PTR pnKey2) const
|
||
{
|
||
IRTLASSERT(m_pfnEqualKeys != NULL);
|
||
return (*m_pfnEqualKeys)(pnKey1, pnKey2);
|
||
}
|
||
|
||
// AddRef or Release a record.
|
||
void _AddRefRecord(const void* pvRecord, int nIncr) const
|
||
{
|
||
IRTLASSERT(pvRecord != NULL && (nIncr == -1 || nIncr == +1));
|
||
(*m_pfnAddRefRecord)(pvRecord, nIncr);
|
||
}
|
||
|
||
// Find a bucket, given its signature. The bucket is locked before
|
||
// returning. Assumes table is already locked, to avoid race conditions.
|
||
CBucket* _FindBucket(DWORD dwSignature, bool fLockForWrite) const
|
||
{
|
||
IRTLASSERT(IsValid());
|
||
IRTLASSERT(m_dwBktAddrMask > 0);
|
||
IRTLASSERT((m_dwBktAddrMask & (m_dwBktAddrMask+1)) == 0); // 00011..111
|
||
IRTLASSERT(m_dwBktAddrMask == (1U << m_nLevel) - 1);
|
||
#ifdef LKR_MASK
|
||
IRTLASSERT(m_dwBktAddrMask1 == ((m_dwBktAddrMask << 1) | 1));
|
||
IRTLASSERT((m_dwBktAddrMask1 & (m_dwBktAddrMask1+1)) == 0);
|
||
#endif // LKR_MASK
|
||
IRTLASSERT(m_iExpansionIdx <= m_dwBktAddrMask);
|
||
IRTLASSERT(0 < m_dwSegBits && m_dwSegBits < 20
|
||
&& m_dwSegSize == (1U << m_dwSegBits)
|
||
&& m_dwSegMask == (m_dwSegSize - 1));
|
||
IRTLASSERT(IsReadLocked() || IsWriteLocked());
|
||
|
||
const DWORD dwBktAddr = _BucketAddress(dwSignature);
|
||
IRTLASSERT(dwBktAddr < m_cActiveBuckets);
|
||
|
||
CBucket* const pbkt = _Bucket(dwBktAddr);
|
||
IRTLASSERT(pbkt != NULL);
|
||
|
||
if (fLockForWrite)
|
||
pbkt->WriteLock();
|
||
else
|
||
pbkt->ReadLock();
|
||
|
||
return pbkt;
|
||
}
|
||
|
||
// Memory allocation wrappers to allow us to simulate allocation
|
||
// failures during testing
|
||
static CDirEntry* const
|
||
_AllocateSegmentDirectory(
|
||
size_t n);
|
||
|
||
static bool
|
||
_FreeSegmentDirectory(
|
||
CDirEntry* paDirSegs);
|
||
|
||
static CNodeClump* const
|
||
_AllocateNodeClump();
|
||
|
||
static bool
|
||
_FreeNodeClump(
|
||
CNodeClump* pnc);
|
||
|
||
CSegment* const
|
||
_AllocateSegment() const;
|
||
|
||
bool
|
||
_FreeSegment(
|
||
CSegment* pseg) const;
|
||
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
static LONG sm_cTables;
|
||
|
||
static const char*
|
||
_LockName()
|
||
{
|
||
LONG l = ++sm_cTables;
|
||
// possible race condition but we don't care, as this is never
|
||
// used in production code
|
||
static char s_szName[CLockStatistics::L_NAMELEN];
|
||
wsprintf(s_szName, "LH%05x", 0xFFFFF & l);
|
||
return s_szName;
|
||
}
|
||
|
||
// Statistics for the table lock
|
||
CLockStatistics _LockStats() const
|
||
{ return m_Lock.Statistics(); }
|
||
#endif // LOCK_INSTRUMENTATION
|
||
|
||
private:
|
||
|
||
// Fields are ordered so as to minimize number of cache lines touched
|
||
|
||
DWORD m_dwSignature; // debugging: id & corruption check
|
||
CHAR m_szName[NAME_SIZE]; // an identifier for debugging
|
||
mutable LK_RETCODE m_lkrcState; // Internal state of table
|
||
mutable TableLock m_Lock; // Lock on entire linear hash table
|
||
|
||
// type-specific function pointers
|
||
PFnExtractKey m_pfnExtractKey; // Extract key from record
|
||
PFnCalcKeyHash m_pfnCalcKeyHash; // Calculate hash signature of key
|
||
PFnEqualKeys m_pfnEqualKeys; // Compare two keys
|
||
PFnAddRefRecord m_pfnAddRefRecord; // AddRef a record
|
||
|
||
LK_TABLESIZE m_lkts; // "size" of table: small, medium, or large
|
||
DWORD m_dwSegBits; // C{Small,Medium,Large}Segment::SEGBITS
|
||
DWORD m_dwSegSize; // C{Small,Medium,Large}Segment::SEGSIZE
|
||
DWORD m_dwSegMask; // C{Small,Medium,Large}Segment::SEGMASK
|
||
double m_MaxLoad; // max load factor (average chain length)
|
||
|
||
DWORD m_dwBktAddrMask; // mask used for address calculation
|
||
#ifdef LKR_MASK
|
||
DWORD m_dwBktAddrMask1; // used in _H1 calculation
|
||
#endif // LKR_MASK
|
||
DWORD m_iExpansionIdx; // address of next bucket to be expanded
|
||
CDirEntry* m_paDirSegs; // directory of table segments
|
||
DWORD m_nLevel; // number of table doublings performed
|
||
DWORD m_cDirSegs; // segment directory size: varies between
|
||
// MIN_DIRSIZE and MAX_DIRSIZE
|
||
DWORD m_cRecords; // number of records in the table
|
||
DWORD m_cActiveBuckets; // number of buckets in use (table size)
|
||
|
||
WORD m_wBucketLockSpins;// default spin count for bucket locks
|
||
|
||
#ifdef LKR_NEWCODE
|
||
const BYTE m_nTableLockType; // for debugging: LOCK_SPINLOCK, etc
|
||
const BYTE m_nBucketLockType;// for debugging: LOCK_SPINLOCK, etc
|
||
const CLKRHashTable* const m_phtParent;// Owning table. NULL => standalone
|
||
|
||
static CLockedDoubleList sm_llGlobalList;// All active CLKRLinearHashTables
|
||
CListEntry m_leGlobalList;
|
||
|
||
void _InsertThisIntoGlobalList()
|
||
{
|
||
// Only add standalone CLKRLinearHashTables to global list.
|
||
// CLKRHashTables have their own global list.
|
||
if (m_phtParent == NULL)
|
||
sm_llGlobalList.InsertHead(&m_leGlobalList);
|
||
}
|
||
|
||
void _RemoveThisFromGlobalList()
|
||
{
|
||
if (m_phtParent == NULL)
|
||
sm_llGlobalList.RemoveEntry(&m_leGlobalList);
|
||
}
|
||
#endif // LKR_NEWCODE
|
||
|
||
// Non-trivial implementation functions
|
||
LK_RETCODE _InsertRecord(const void* pvRecord, DWORD dwSignature,
|
||
bool fOverwrite);
|
||
LK_RETCODE _DeleteKey(const DWORD_PTR pnKey, DWORD dwSignature);
|
||
LK_RETCODE _DeleteRecord(const void* pvRecord, DWORD dwSignature);
|
||
bool _DeleteNode(CBucket* pbkt, CNodeClump*& rpnc,
|
||
CNodeClump*& rpncPrev, int& riNode);
|
||
LK_RETCODE _FindKey(const DWORD_PTR pnKey, DWORD dwSignature,
|
||
const void** ppvRecord) const;
|
||
LK_RETCODE _FindRecord(const void* pvRecord, DWORD dwSignature) const;
|
||
|
||
#ifdef LKR_COMPACT_DELETE
|
||
// returns count of errors in compacted state => 0 is good
|
||
int _IsNodeCompact(CBucket* const pbkt) const;
|
||
#endif // LKR_COMPACT_DELETE
|
||
|
||
|
||
// Predicate functions
|
||
static LK_PREDICATE _PredTrue(const void* /*pvRecord*/, void* /*pvState*/)
|
||
{ return LKP_PERFORM; }
|
||
|
||
DWORD _Apply(PFnRecordAction pfnAction, void* pvState,
|
||
LK_LOCKTYPE lkl, LK_PREDICATE& rlkp);
|
||
DWORD _ApplyIf(PFnRecordPred pfnPredicate,
|
||
PFnRecordAction pfnAction, void* pvState,
|
||
LK_LOCKTYPE lkl, LK_PREDICATE& rlkp);
|
||
DWORD _DeleteIf(PFnRecordPred pfnPredicate, void* pvState,
|
||
LK_PREDICATE& rlkp);
|
||
void _Clear(bool fShrinkDirectory);
|
||
|
||
|
||
void _SetSegVars(LK_TABLESIZE lkts);
|
||
LK_RETCODE _Expand();
|
||
LK_RETCODE _Contract();
|
||
LK_RETCODE _SplitRecordSet(CNodeClump* pncOldTarget,
|
||
CNodeClump* pncNewTarget,
|
||
DWORD iExpansionIdx,
|
||
DWORD dwBktAddrMask,
|
||
DWORD dwNewBkt,
|
||
CNodeClump* pncFreeList);
|
||
LK_RETCODE _MergeRecordSets(CBucket* pbktNewTarget,
|
||
CNodeClump* pncOldList,
|
||
CNodeClump* pncFreeList);
|
||
|
||
// Private copy ctor and op= to prevent compiler synthesizing them.
|
||
// Must provide a (bad) implementation because we export instantiations.
|
||
// TODO: implement these properly; they could be useful.
|
||
CLKRLinearHashTable(const CLKRLinearHashTable&)
|
||
: m_dwSignature(SIGNATURE_FREE)
|
||
#ifdef LOCK_INSTRUMENTATION
|
||
, m_Lock(NULL)
|
||
#endif // LOCK_INSTRUMENTATION
|
||
#ifdef LKR_NEWCODE
|
||
, m_nTableLockType(0),
|
||
m_nBucketLockType(0),
|
||
m_phtParent(NULL)
|
||
#endif // LKR_NEWCODE
|
||
{*(BYTE*)NULL;}
|
||
|
||
CLKRLinearHashTable& operator=(const CLKRLinearHashTable&)
|
||
{return *(CLKRLinearHashTable*)NULL;}
|
||
|
||
#ifdef LKR_NEWCODE
|
||
private:
|
||
// This ctor is used by CLKRHashTable
|
||
CLKRLinearHashTable(
|
||
LPCSTR pszName, // An identifier for debugging
|
||
PFnExtractKey pfnExtractKey, // Extract key from record
|
||
PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
|
||
PFnEqualKeys pfnEqualKeys, // Compare two keys
|
||
PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
|
||
double maxload, // Upperbound on average chain length
|
||
DWORD initsize, // Initial size of hash table.
|
||
CLKRHashTable* phtParent // Owning table.
|
||
);
|
||
#endif // LKR_NEWCODE
|
||
|
||
LK_RETCODE
|
||
_Initialize(
|
||
PFnExtractKey pfnExtractKey,
|
||
PFnCalcKeyHash pfnCalcKeyHash,
|
||
PFnEqualKeys pfnEqualKeys,
|
||
PFnAddRefRecord pfnAddRefRecord,
|
||
LPCSTR pszName,
|
||
double maxload,
|
||
DWORD initsize);
|
||
|
||
public:
|
||
CLKRLinearHashTable(
|
||
LPCSTR pszName, // An identifier for debugging
|
||
PFnExtractKey pfnExtractKey, // Extract key from record
|
||
PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
|
||
PFnEqualKeys pfnEqualKeys, // Compare two keys
|
||
PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
|
||
double maxload=LK_DFLT_MAXLOAD,// Upperbound on average chain length
|
||
DWORD initsize=LK_DFLT_INITSIZE, // Initial size of hash table.
|
||
DWORD num_subtbls=LK_DFLT_NUM_SUBTBLS // for signature compatiblity
|
||
// with CLKRHashTable
|
||
);
|
||
|
||
~CLKRLinearHashTable();
|
||
|
||
static const char* ClassName() {return "CLKRLinearHashTable";}
|
||
int NumSubTables() const {return 1;}
|
||
|
||
static LK_TABLESIZE NumSubTables(DWORD& rinitsize, DWORD& rnum_subtbls);
|
||
|
||
// Insert a new record into hash table.
|
||
// Returns LK_SUCCESS if all OK, LK_KEY_EXISTS if same key already
|
||
// exists (unless fOverwrite), LK_ALLOC_FAIL if out of space,
|
||
// or LK_BAD_RECORD for a bad record.
|
||
LK_RETCODE InsertRecord(const void* pvRecord, bool fOverwrite=false)
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (pvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
return _InsertRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)),
|
||
fOverwrite);
|
||
}
|
||
|
||
// Delete record with the given key.
|
||
// Returns LK_SUCCESS if all OK, or LK_NO_SUCH_KEY if not found
|
||
LK_RETCODE DeleteKey(const DWORD_PTR pnKey)
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
#endif
|
||
|
||
return _DeleteKey(pnKey, _CalcKeyHash(pnKey));
|
||
}
|
||
|
||
// Delete a record from the table, if present.
|
||
// Returns LK_SUCCESS if all OK, or LK_NO_SUCH_KEY if not found
|
||
LK_RETCODE DeleteRecord(const void* pvRecord)
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (pvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
return _DeleteRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)));
|
||
}
|
||
|
||
// Find record with given key.
|
||
// Returns: LK_SUCCESS, if record found (record is returned in *ppvRecord)
|
||
// LK_BAD_RECORD, if ppvRecord is invalid
|
||
// LK_NO_SUCH_KEY, if no record with given key value was found
|
||
// LK_UNUSABLE, if hash table not in usable state
|
||
// Note: the record is AddRef'd. You must decrement the reference
|
||
// count when you are finished with the record (if you're implementing
|
||
// refcounting semantics).
|
||
LK_RETCODE FindKey(const DWORD_PTR pnKey,
|
||
const void** ppvRecord) const
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (ppvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
return _FindKey(pnKey, _CalcKeyHash(pnKey), ppvRecord);
|
||
}
|
||
|
||
// Sees if the record is contained in the table
|
||
// Returns: LK_SUCCESS, if record found
|
||
// LK_BAD_RECORD, if pvRecord is invalid
|
||
// LK_NO_SUCH_KEY, if record is not in the table
|
||
// LK_UNUSABLE, if hash table not in usable state
|
||
// Note: the record is *not* AddRef'd.
|
||
LK_RETCODE FindRecord(const void* pvRecord) const
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (pvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
return _FindRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)));
|
||
}
|
||
|
||
// Walk the hash table, applying pfnAction to all records.
|
||
// Locks the whole table for the duration with either a (possibly
|
||
// shared) readlock or a writelock, according to lkl.
|
||
// Loop is aborted if pfnAction returns LKA_ABORT.
|
||
// Returns the number of successful applications.
|
||
DWORD Apply(PFnRecordAction pfnAction,
|
||
void* pvState=NULL,
|
||
LK_LOCKTYPE lkl=LKL_READLOCK);
|
||
|
||
// Walk the hash table, applying pfnAction to any records that match
|
||
// pfnPredicate. Locks the whole table for the duration with either
|
||
// a (possibly shared) readlock or a writelock, according to lkl.
|
||
// Loop is aborted if pfnAction returns LKA_ABORT.
|
||
// Returns the number of successful applications.
|
||
DWORD ApplyIf(PFnRecordPred pfnPredicate,
|
||
PFnRecordAction pfnAction,
|
||
void* pvState=NULL,
|
||
LK_LOCKTYPE lkl=LKL_READLOCK);
|
||
|
||
// Delete any records that match pfnPredicate.
|
||
// Locks the table for the duration with a writelock.
|
||
// Returns the number of deletions.
|
||
//
|
||
// Do *not* walk the hash table by hand with an iterator and call
|
||
// DeleteKey. The iterator will end up pointing to garbage.
|
||
DWORD DeleteIf(PFnRecordPred pfnPredicate,
|
||
void* pvState=NULL);
|
||
|
||
// Check table for consistency. Returns 0 if okay, or the number of
|
||
// errors otherwise.
|
||
int CheckTable() const;
|
||
|
||
// Prints the table (to where??)
|
||
void Print() const;
|
||
|
||
// Remove all data from the table
|
||
void Clear()
|
||
{
|
||
WriteLock();
|
||
_Clear(true);
|
||
WriteUnlock();
|
||
}
|
||
|
||
// Number of elements in the table
|
||
DWORD Size() const
|
||
{ return m_cRecords; }
|
||
|
||
// Maximum possible number of elements in the table
|
||
DWORD MaxSize() const
|
||
{ return static_cast<DWORD>(m_MaxLoad * MAX_DIRSIZE * m_dwSegSize); }
|
||
|
||
// Get hash table statistics
|
||
CLKRHashTableStats GetStatistics() const;
|
||
|
||
// Is the hash table usable?
|
||
bool IsUsable() const
|
||
{ return (m_lkrcState == LK_SUCCESS); }
|
||
|
||
// Is the hash table consistent and correct?
|
||
bool IsValid() const
|
||
{
|
||
bool f = (m_lkrcState == LK_SUCCESS // serious internal failure?
|
||
&& m_paDirSegs != NULL
|
||
&& (MIN_DIRSIZE & (MIN_DIRSIZE-1)) == 0 // == (1 << N)
|
||
&& (MAX_DIRSIZE & (MAX_DIRSIZE-1)) == 0
|
||
&& MAX_DIRSIZE > MIN_DIRSIZE
|
||
&& MIN_DIRSIZE <= m_cDirSegs && m_cDirSegs <= MAX_DIRSIZE
|
||
&& (m_cDirSegs & (m_cDirSegs-1)) == 0
|
||
&& m_pfnExtractKey != NULL
|
||
&& m_pfnCalcKeyHash != NULL
|
||
&& m_pfnEqualKeys != NULL
|
||
&& m_cActiveBuckets > 0
|
||
&& ValidSignature()
|
||
);
|
||
if (!f)
|
||
m_lkrcState = LK_UNUSABLE;
|
||
return f;
|
||
}
|
||
|
||
// Set the spin count on the table lock
|
||
void SetTableLockSpinCount(WORD wSpins)
|
||
{ m_Lock.SetSpinCount(wSpins); }
|
||
|
||
// Get the spin count on the table lock
|
||
WORD GetTableLockSpinCount()
|
||
{ return m_Lock.GetSpinCount(); }
|
||
|
||
// Set/Get the spin count on the bucket locks
|
||
void SetBucketLockSpinCount(WORD wSpins);
|
||
WORD GetBucketLockSpinCount();
|
||
|
||
enum {
|
||
SIGNATURE = (('L') | ('K' << 8) | ('L' << 16) | ('H' << 24)),
|
||
SIGNATURE_FREE = (('L') | ('K' << 8) | ('L' << 16) | ('x' << 24)),
|
||
};
|
||
|
||
bool
|
||
ValidSignature() const
|
||
{ return m_dwSignature == SIGNATURE;}
|
||
|
||
|
||
// Lock manipulators
|
||
|
||
// Lock the table (exclusively) for writing
|
||
void WriteLock()
|
||
{ m_Lock.WriteLock(); }
|
||
|
||
// Lock the table (possibly shared) for reading
|
||
void ReadLock() const
|
||
{ m_Lock.ReadLock(); }
|
||
|
||
// Unlock the table for writing
|
||
void WriteUnlock() const
|
||
{ m_Lock.WriteUnlock(); }
|
||
|
||
// Unlock the table for reading
|
||
void ReadUnlock() const
|
||
{ m_Lock.ReadUnlock(); }
|
||
|
||
// Is the table already locked for writing?
|
||
bool IsWriteLocked() const
|
||
{ return m_Lock.IsWriteLocked(); }
|
||
|
||
// Is the table already locked for reading?
|
||
bool IsReadLocked() const
|
||
{ return m_Lock.IsReadLocked(); }
|
||
|
||
// Is the table unlocked for writing?
|
||
bool IsWriteUnlocked() const
|
||
{ return m_Lock.IsWriteUnlocked(); }
|
||
|
||
// Is the table unlocked for reading?
|
||
bool IsReadUnlocked() const
|
||
{ return m_Lock.IsReadUnlocked(); }
|
||
|
||
// LKRHASH_ALLOCATOR_DEFINITIONS(CLKRLinearHashTable);
|
||
|
||
public:
|
||
|
||
// Iterators can be used to walk the table. To ensure a consistent
|
||
// view of the data, the iterator locks the whole table. This can
|
||
// have a negative effect upon performance, because no other thread
|
||
// can do anything with the table. Use with care.
|
||
//
|
||
// You should not use an iterator to walk the table, calling DeleteKey,
|
||
// as the iterator will end up pointing to garbage.
|
||
//
|
||
// Use Apply, ApplyIf, or DeleteIf instead of iterators to safely
|
||
// walk the tree.
|
||
//
|
||
// Note that iterators acquire a reference to the record pointed to
|
||
// and release that reference as soon as the iterator is incremented.
|
||
// In other words, this code is safe:
|
||
// lkrc = ht.IncrementIterator(&iter);
|
||
// // assume lkrc == LK_SUCCESS for the sake of this example
|
||
// CMyHashTable::Record* pRec = iter.Record();
|
||
// Foo(pRec); // uses pRec but doesn't hang on to it
|
||
// lkrc = ht.IncrementIterator(&iter);
|
||
//
|
||
// But this code is not safe because pRec is used out of the scope of the
|
||
// iterator that provided it:
|
||
// lkrc = ht.IncrementIterator(&iter);
|
||
// CMyHashTable::Record* pRec = iter.Record();
|
||
// // Should call ht.AddRefRecord(pRec, +1) here
|
||
// lkrc = ht.IncrementIterator(&iter);
|
||
// Foo(pRec);
|
||
//
|
||
// If record has no reference-counting semantics, then you can ignore
|
||
// the above remarks about scope.
|
||
|
||
|
||
class CIterator
|
||
{
|
||
protected:
|
||
friend class CLKRLinearHashTable;
|
||
|
||
CLKRLinearHashTable* m_plht; // which linear hash table?
|
||
DWORD m_dwBucketAddr; // bucket index
|
||
CNodeClump* m_pnc; // a CNodeClump in bucket
|
||
int m_iNode; // offset within m_pnc
|
||
LK_LOCKTYPE m_lkl; // readlock or writelock?
|
||
|
||
private:
|
||
// Private copy ctor and op= to prevent compiler synthesizing them.
|
||
// Must provide (bad) implementation because we export instantiations.
|
||
CIterator(const CIterator&) {*(BYTE*)NULL;}
|
||
CIterator& operator=(const CIterator&) {return *(CIterator*)NULL;}
|
||
|
||
public:
|
||
CIterator(
|
||
LK_LOCKTYPE lkl=LKL_WRITELOCK)
|
||
: m_plht(NULL),
|
||
m_dwBucketAddr(0),
|
||
m_pnc(NULL),
|
||
m_iNode(-1),
|
||
m_lkl(lkl)
|
||
{}
|
||
|
||
// Return the record associated with this iterator
|
||
const void* Record() const
|
||
{
|
||
IRTLASSERT(IsValid());
|
||
|
||
return ((m_pnc != NULL
|
||
&& m_iNode >= 0
|
||
&& m_iNode < CLKRLinearHashTable::NODES_PER_CLUMP)
|
||
? m_pnc->m_pvNode[m_iNode]
|
||
: NULL);
|
||
}
|
||
|
||
// Return the key associated with this iterator
|
||
const DWORD_PTR Key() const
|
||
{
|
||
IRTLASSERT(m_plht != NULL);
|
||
const void* pRec = Record();
|
||
return ((pRec != NULL && m_plht != NULL)
|
||
? m_plht->_ExtractKey(pRec)
|
||
: NULL);
|
||
}
|
||
|
||
bool IsValid() const
|
||
{
|
||
return ((m_plht != NULL)
|
||
&& (m_pnc != NULL)
|
||
&& (0 <= m_iNode
|
||
&& m_iNode < CLKRLinearHashTable::NODES_PER_CLUMP)
|
||
&& (!m_pnc->IsEmptyNode(m_iNode)));
|
||
}
|
||
|
||
// Delete the record that the iterator points to. Does an implicit
|
||
// IncrementIterator after deletion.
|
||
LK_RETCODE DeleteRecord();
|
||
|
||
// Change the record that the iterator points to. The new record
|
||
// must have the same key as the old one.
|
||
LK_RETCODE ChangeRecord(const void* pNewRec);
|
||
};
|
||
|
||
// Const iterators for readonly access. You must use these with
|
||
// const CLKRLinearHashTables.
|
||
class CConstIterator : public CIterator
|
||
{
|
||
private:
|
||
// Private, unimplemented copy ctor and op= to prevent
|
||
// compiler synthesizing them.
|
||
CConstIterator(const CConstIterator&);
|
||
CConstIterator& operator=(const CConstIterator&);
|
||
|
||
public:
|
||
CConstIterator()
|
||
: CIterator(LKL_READLOCK)
|
||
{}
|
||
};
|
||
|
||
private:
|
||
// The public APIs lock the table. The private ones, which are used
|
||
// directly by CLKRHashTable, don't.
|
||
LK_RETCODE _InitializeIterator(CIterator* piter);
|
||
LK_RETCODE _CloseIterator(CIterator* piter);
|
||
|
||
public:
|
||
// Initialize the iterator to point to the first item in the hash table
|
||
// Returns LK_SUCCESS, LK_NO_MORE_ELEMENTS, or LK_BAD_ITERATOR.
|
||
LK_RETCODE InitializeIterator(CIterator* piter)
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_plht == NULL);
|
||
if (piter == NULL || piter->m_plht != NULL)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
if (piter->m_lkl == LKL_WRITELOCK)
|
||
WriteLock();
|
||
else
|
||
ReadLock();
|
||
|
||
return _InitializeIterator(piter);
|
||
}
|
||
|
||
// The const iterator version
|
||
LK_RETCODE InitializeIterator(CConstIterator* piter) const
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_plht == NULL);
|
||
IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
|
||
|
||
if (piter == NULL || piter->m_plht != NULL
|
||
|| piter->m_lkl == LKL_WRITELOCK)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
ReadLock();
|
||
return const_cast<CLKRLinearHashTable*>(this)
|
||
->_InitializeIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
// Move the iterator on to the next item in the table.
|
||
// Returns LK_SUCCESS, LK_NO_MORE_ELEMENTS, or LK_BAD_ITERATOR.
|
||
LK_RETCODE IncrementIterator(CIterator* piter);
|
||
|
||
LK_RETCODE IncrementIterator(CConstIterator* piter) const
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_plht == this);
|
||
IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
|
||
|
||
if (piter == NULL || piter->m_plht != this
|
||
|| piter->m_lkl == LKL_WRITELOCK)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
return const_cast<CLKRLinearHashTable*>(this)
|
||
->IncrementIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
// Close the iterator.
|
||
LK_RETCODE CloseIterator(CIterator* piter)
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_plht == this);
|
||
if (piter == NULL || piter->m_plht != this)
|
||
return LK_BAD_ITERATOR;
|
||
_CloseIterator(piter);
|
||
|
||
if (piter->m_lkl == LKL_WRITELOCK)
|
||
WriteUnlock();
|
||
else
|
||
ReadUnlock();
|
||
|
||
return LK_SUCCESS;
|
||
};
|
||
|
||
// Close the CConstIterator
|
||
LK_RETCODE CloseIterator(CConstIterator* piter) const
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_plht == this);
|
||
IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
|
||
|
||
if (piter == NULL || piter->m_plht != this
|
||
|| piter->m_lkl == LKL_WRITELOCK)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
const_cast<CLKRLinearHashTable*>(this)
|
||
->_CloseIterator(static_cast<CIterator*>(piter));
|
||
|
||
ReadUnlock();
|
||
return LK_SUCCESS;
|
||
};
|
||
};
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// CLKRHashTable
|
||
//
|
||
// To improve concurrency, a hash table is divided into a number of
|
||
// (independent) subtables. Each subtable is a linear hash table. The
|
||
// number of subtables is defined when the table is created and remains
|
||
// fixed thereafter. Records are assigned to subtables based on their
|
||
// hashed key.
|
||
//
|
||
// For small or low-contention hashtables, you can bypass this
|
||
// thin wrapper and use CLKRLinearHashTable directly. The methods are
|
||
// documented in the declarations for CLKRHashTable (above).
|
||
//--------------------------------------------------------------------
|
||
|
||
class IRTL_DLLEXP CLKRHashTable
|
||
{
|
||
private:
|
||
typedef CLKRLinearHashTable SubTable;
|
||
|
||
public:
|
||
typedef SubTable::TableLock TableLock;
|
||
typedef SubTable::BucketLock BucketLock;
|
||
|
||
class CIterator;
|
||
friend class CLKRHashTable::CIterator;
|
||
friend class CLKRLinearHashTable;
|
||
|
||
// aliases for convenience
|
||
enum {
|
||
NAME_SIZE = SubTable::NAME_SIZE,
|
||
HASH_INVALID_SIGNATURE = SubTable::HASH_INVALID_SIGNATURE,
|
||
NODES_PER_CLUMP = SubTable::NODES_PER_CLUMP,
|
||
};
|
||
|
||
private:
|
||
// Hash table parameters
|
||
DWORD m_dwSignature; // debugging: id & corruption check
|
||
CHAR m_szName[NAME_SIZE]; // an identifier for debugging
|
||
DWORD m_cSubTables; // number of subtables
|
||
SubTable** m_palhtDir; // array of subtables
|
||
|
||
// type-specific function pointers
|
||
PFnExtractKey m_pfnExtractKey;
|
||
PFnCalcKeyHash m_pfnCalcKeyHash;
|
||
mutable LK_RETCODE m_lkrcState; // Internal state of table
|
||
|
||
#ifdef LKR_NEWCODE
|
||
static CLockedDoubleList sm_llGlobalList; // All active CLKRHashTables
|
||
CListEntry m_leGlobalList;
|
||
|
||
void
|
||
_InsertThisIntoGlobalList()
|
||
{
|
||
sm_llGlobalList.InsertHead(&m_leGlobalList);
|
||
}
|
||
|
||
void
|
||
_RemoveThisFromGlobalList()
|
||
{
|
||
sm_llGlobalList.RemoveEntry(&m_leGlobalList);
|
||
}
|
||
#endif // LKR_NEWCODE
|
||
|
||
LKRHASH_GLOBAL_LOCK_DECLARATIONS();
|
||
|
||
// Private copy ctor and op= to prevent compiler synthesizing them.
|
||
// Must provide a (bad) implementation because we export instantiations.
|
||
// TODO: implement these properly; they could be useful.
|
||
CLKRHashTable(const CLKRHashTable&) {*(BYTE*)NULL;}
|
||
CLKRHashTable& operator=(const CLKRHashTable&) {return *(CLKRHashTable*)NULL;}
|
||
|
||
|
||
// Extract the key from the record
|
||
const DWORD_PTR _ExtractKey(const void* pvRecord) const
|
||
{
|
||
IRTLASSERT(pvRecord != NULL);
|
||
IRTLASSERT(m_pfnExtractKey != NULL);
|
||
#ifdef LKR_SUBTABLE
|
||
return (*m_pfnExtractKey)(pvRecord);
|
||
#else
|
||
return (pvRecord != NULL) ? (*m_pfnExtractKey)(pvRecord) : NULL;
|
||
#endif
|
||
}
|
||
|
||
// Hash the key
|
||
DWORD _CalcKeyHash(const DWORD_PTR pnKey) const
|
||
{
|
||
// Note pnKey==0 is acceptable, as the real key type could be an int
|
||
IRTLASSERT(m_pfnCalcKeyHash != NULL);
|
||
DWORD dwHash = (*m_pfnCalcKeyHash)(pnKey);
|
||
// We forcibly scramble the result to help ensure a better distribution
|
||
dwHash = HashScramble(dwHash);
|
||
IRTLASSERT(dwHash != HASH_INVALID_SIGNATURE);
|
||
return dwHash;
|
||
}
|
||
|
||
// Use the key's hash signature to multiplex into a subtable
|
||
SubTable* _SubTable(DWORD dwSignature) const
|
||
{
|
||
IRTLASSERT(m_lkrcState == LK_SUCCESS
|
||
&& m_palhtDir != NULL && m_cSubTables > 0);
|
||
#ifndef LKR_SUBTABLE
|
||
if (m_lkrcState == LK_SUCCESS)
|
||
{
|
||
#endif
|
||
const DWORD PRIME = 1048583UL; // used to scramble the hash sig
|
||
DWORD index = (dwSignature % PRIME) % m_cSubTables;
|
||
return m_palhtDir[index];
|
||
#ifndef LKR_SUBTABLE
|
||
}
|
||
else
|
||
return NULL;
|
||
#endif
|
||
}
|
||
|
||
// Memory allocation wrappers to allow us to simulate allocation
|
||
// failures during testing
|
||
static SubTable** const
|
||
_AllocateSubTableArray(
|
||
size_t n);
|
||
|
||
static bool
|
||
_FreeSubTableArray(
|
||
SubTable** palht);
|
||
|
||
static SubTable* const
|
||
_AllocateSubTable(
|
||
LPCSTR pszName, // An identifier for debugging
|
||
PFnExtractKey pfnExtractKey, // Extract key from record
|
||
PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
|
||
PFnEqualKeys pfnEqualKeys, // Compare two keys
|
||
PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
|
||
double maxload, // Upperbound on average chain length
|
||
DWORD initsize, // Initial size of hash table.
|
||
CLKRHashTable* phtParent // Owning table.
|
||
);
|
||
|
||
static bool
|
||
_FreeSubTable(
|
||
SubTable* plht);
|
||
|
||
public:
|
||
CLKRHashTable(
|
||
LPCSTR pszName, // An identifier for debugging
|
||
PFnExtractKey pfnExtractKey, // Extract key from record
|
||
PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
|
||
PFnEqualKeys pfnEqualKeys, // Compare two keys
|
||
PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
|
||
double maxload=LK_DFLT_MAXLOAD, // bound on avg chain length
|
||
DWORD initsize=LK_DFLT_INITSIZE, // Initial size of hash table.
|
||
DWORD num_subtbls=LK_DFLT_NUM_SUBTBLS // #subordinate hash tables.
|
||
);
|
||
|
||
~CLKRHashTable();
|
||
|
||
static const char* ClassName() {return "CLKRHashTable";}
|
||
int NumSubTables() const {return m_cSubTables;}
|
||
|
||
static LK_TABLESIZE NumSubTables(DWORD& rinitsize, DWORD& rnum_subtbls);
|
||
|
||
|
||
// Thin wrappers for the corresponding methods in CLKRLinearHashTable
|
||
LK_RETCODE InsertRecord(const void* pvRecord, bool fOverwrite=false)
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (pvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op
|
||
DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord));
|
||
SubTable* const pst = _SubTable(hash_val);
|
||
#ifdef LKR_SUBTABLE
|
||
LK_RETCODE lk = pst->_InsertRecord(pvRecord, hash_val, fOverwrite);
|
||
#else
|
||
LK_RETCODE lk = (pst != NULL
|
||
? pst->_InsertRecord(pvRecord, hash_val,
|
||
fOverwrite)
|
||
: LK_UNUSABLE);
|
||
#endif
|
||
LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op
|
||
return lk;
|
||
}
|
||
|
||
LK_RETCODE DeleteKey(const DWORD_PTR pnKey)
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
#endif
|
||
|
||
LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op
|
||
DWORD hash_val = _CalcKeyHash(pnKey);
|
||
SubTable* const pst = _SubTable(hash_val);
|
||
#ifdef LKR_SUBTABLE
|
||
LK_RETCODE lk = pst->_DeleteKey(pnKey, hash_val);
|
||
#else
|
||
LK_RETCODE lk = (pst != NULL
|
||
? pst->_DeleteKey(pnKey, hash_val)
|
||
: LK_UNUSABLE);
|
||
#endif
|
||
LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op
|
||
return lk;
|
||
}
|
||
|
||
LK_RETCODE DeleteRecord(const void* pvRecord)
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (pvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op
|
||
DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord));
|
||
SubTable* const pst = _SubTable(hash_val);
|
||
#ifdef LKR_SUBTABLE
|
||
LK_RETCODE lk = pst->_DeleteRecord(pvRecord, hash_val);
|
||
#else
|
||
LK_RETCODE lk = (pst != NULL
|
||
? pst->_DeleteRecord(pvRecord, hash_val)
|
||
: LK_UNUSABLE);
|
||
#endif
|
||
LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op
|
||
return lk;
|
||
}
|
||
|
||
LK_RETCODE FindKey(const DWORD_PTR pnKey,
|
||
const void** ppvRecord) const
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (ppvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
LKRHASH_GLOBAL_READ_LOCK(); // usu. no-op
|
||
DWORD hash_val = _CalcKeyHash(pnKey);
|
||
SubTable* const pst = _SubTable(hash_val);
|
||
#ifdef LKR_SUBTABLE
|
||
LK_RETCODE lkrc = pst->_FindKey(pnKey, hash_val, ppvRecord);
|
||
#else
|
||
LK_RETCODE lkrc = (pst != NULL
|
||
? pst->_FindKey(pnKey, hash_val, ppvRecord)
|
||
: LK_UNUSABLE);
|
||
#endif
|
||
LKRHASH_GLOBAL_READ_UNLOCK(); // usu. no-op
|
||
return lkrc;
|
||
}
|
||
|
||
LK_RETCODE FindRecord(const void* pvRecord) const
|
||
{
|
||
#ifdef LKR_SUBTABLE
|
||
if (!IsUsable())
|
||
return m_lkrcState;
|
||
|
||
if (pvRecord == NULL)
|
||
return LK_BAD_RECORD;
|
||
#endif
|
||
|
||
LKRHASH_GLOBAL_READ_LOCK(); // usu. no-op
|
||
DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord));
|
||
SubTable* const pst = _SubTable(hash_val);
|
||
#ifdef LKR_SUBTABLE
|
||
LK_RETCODE lkrc = pst->_FindRecord(pvRecord, hash_val);
|
||
#else
|
||
LK_RETCODE lkrc = (pst != NULL
|
||
? pst->_FindRecord(pvRecord, hash_val)
|
||
: LK_UNUSABLE);
|
||
#endif
|
||
LKRHASH_GLOBAL_READ_UNLOCK(); // usu. no-op
|
||
return lkrc;
|
||
}
|
||
|
||
DWORD Apply(PFnRecordAction pfnAction,
|
||
void* pvState=NULL,
|
||
LK_LOCKTYPE lkl=LKL_READLOCK);
|
||
DWORD ApplyIf(PFnRecordPred pfnPredicate,
|
||
PFnRecordAction pfnAction,
|
||
void* pvState=NULL,
|
||
LK_LOCKTYPE lkl=LKL_READLOCK);
|
||
DWORD DeleteIf(PFnRecordPred pfnPredicate,
|
||
void* pvState=NULL);
|
||
void Clear();
|
||
|
||
int CheckTable() const;
|
||
void Print() const;
|
||
DWORD Size() const;
|
||
DWORD MaxSize() const;
|
||
CLKRHashTableStats GetStatistics() const;
|
||
bool IsValid() const;
|
||
|
||
void SetTableLockSpinCount(WORD wSpins);
|
||
WORD GetTableLockSpinCount();
|
||
void SetBucketLockSpinCount(WORD wSpins);
|
||
WORD GetBucketLockSpinCount();
|
||
|
||
enum {
|
||
SIGNATURE = (('L') | ('K' << 8) | ('H' << 16) | ('T' << 24)),
|
||
SIGNATURE_FREE = (('L') | ('K' << 8) | ('H' << 16) | ('x' << 24)),
|
||
};
|
||
|
||
bool
|
||
ValidSignature() const
|
||
{ return m_dwSignature == SIGNATURE;}
|
||
|
||
// Is the hash table usable?
|
||
bool IsUsable() const
|
||
{ return (m_lkrcState == LK_SUCCESS); }
|
||
|
||
void WriteLock();
|
||
void ReadLock() const;
|
||
void WriteUnlock() const;
|
||
void ReadUnlock() const;
|
||
bool IsWriteLocked() const;
|
||
bool IsReadLocked() const;
|
||
bool IsWriteUnlocked() const;
|
||
bool IsReadUnlocked() const;
|
||
|
||
|
||
// LKRHASH_ALLOCATOR_DEFINITIONS(CLKRHashTable);
|
||
|
||
public:
|
||
typedef SubTable::CIterator CLHTIterator;
|
||
|
||
class CIterator : public CLHTIterator
|
||
{
|
||
protected:
|
||
friend class CLKRHashTable;
|
||
|
||
CLKRHashTable* m_pht; // which hash table?
|
||
int m_ist; // which subtable
|
||
|
||
private:
|
||
// Private copy ctor and op= to prevent compiler synthesizing them.
|
||
// Must provide (bad) implementation because we export instantiations.
|
||
CIterator(const CIterator&) {*(BYTE*)NULL;}
|
||
CIterator& operator=(const CIterator&) {return *(CIterator*)NULL;}
|
||
|
||
public:
|
||
CIterator(
|
||
LK_LOCKTYPE lkl=LKL_WRITELOCK)
|
||
: CLHTIterator(lkl),
|
||
m_pht(NULL),
|
||
m_ist(-1)
|
||
{}
|
||
|
||
const void* Record() const
|
||
{
|
||
IRTLASSERT(IsValid());
|
||
|
||
// This is a hack to work around a compiler bug. Calling
|
||
// CLHTIterator::Record calls this function recursively until
|
||
// the stack overflows.
|
||
const CLHTIterator* pBase = static_cast<const CLHTIterator*>(this);
|
||
return pBase->Record();
|
||
}
|
||
|
||
const DWORD_PTR Key() const
|
||
{
|
||
IRTLASSERT(IsValid());
|
||
const CLHTIterator* pBase = static_cast<const CLHTIterator*>(this);
|
||
return pBase->Key();
|
||
}
|
||
|
||
bool IsValid() const
|
||
{
|
||
const CLHTIterator* pBase = static_cast<const CLHTIterator*>(this);
|
||
return (m_pht != NULL && m_ist >= 0 && pBase->IsValid());
|
||
}
|
||
};
|
||
|
||
// Const iterators for readonly access
|
||
class CConstIterator : public CIterator
|
||
{
|
||
private:
|
||
// Private, unimplemented copy ctor and op= to prevent
|
||
// compiler synthesizing them.
|
||
CConstIterator(const CConstIterator&);
|
||
CConstIterator& operator=(const CConstIterator&);
|
||
|
||
public:
|
||
CConstIterator()
|
||
: CIterator(LKL_READLOCK)
|
||
{}
|
||
};
|
||
|
||
|
||
public:
|
||
LK_RETCODE InitializeIterator(CIterator* piter);
|
||
LK_RETCODE IncrementIterator(CIterator* piter);
|
||
LK_RETCODE CloseIterator(CIterator* piter);
|
||
|
||
LK_RETCODE InitializeIterator(CConstIterator* piter) const
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_pht == NULL);
|
||
IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
|
||
|
||
if (piter == NULL || piter->m_pht != NULL
|
||
|| piter->m_lkl == LKL_WRITELOCK)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
return const_cast<CLKRHashTable*>(this)
|
||
->InitializeIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
LK_RETCODE IncrementIterator(CConstIterator* piter) const
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_pht == this);
|
||
IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
|
||
|
||
if (piter == NULL || piter->m_pht != this
|
||
|| piter->m_lkl == LKL_WRITELOCK)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
return const_cast<CLKRHashTable*>(this)
|
||
->IncrementIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
LK_RETCODE CloseIterator(CConstIterator* piter) const
|
||
{
|
||
IRTLASSERT(piter != NULL && piter->m_pht == this);
|
||
IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
|
||
|
||
if (piter == NULL || piter->m_pht != this
|
||
|| piter->m_lkl == LKL_WRITELOCK)
|
||
return LK_BAD_ITERATOR;
|
||
|
||
return const_cast<CLKRHashTable*>(this)
|
||
->CloseIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
};
|
||
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// A typesafe wrapper for CLKRHashTable (or CLKRLinearHashTable).
|
||
//
|
||
// * _Derived must derive from CTypedHashTable and provide certain member
|
||
// functions. It's needed for various downcasting operations. See
|
||
// CStringTestHashTable and CNumberTestHashTable below.
|
||
// * _Record is the type of the record. C{Linear}HashTable will store
|
||
// pointers to _Record.
|
||
// * _Key is the type of the key. _Key is used directly; i.e., it is
|
||
// not assumed to be a pointer type. C{Linear}HashTable assumes that
|
||
// the key is stored in the associated record. See the comments
|
||
// at the declaration of PFnExtractKey for more details.
|
||
//
|
||
// (optional parameters):
|
||
// * _BaseHashTable is the base hash table: CLKRHashTable or
|
||
/// CLKRLinearHashTable
|
||
// * _BaseIterator is the iterator type, _BaseHashTable::CIterator
|
||
//
|
||
// CTypedHashTable could derive directly from CLKRLinearHashTable, if you
|
||
// don't need the extra overhead of CLKRHashTable (which is quite low).
|
||
//
|
||
// You may need to add the following line to your code to disable
|
||
// warning messages about truncating extremly long identifiers.
|
||
// #pragma warning (disable : 4786)
|
||
//--------------------------------------------------------------------
|
||
|
||
#define LKRHASH_HACKY_CAST(T, pv) ((T) (UINT_PTR) (pv))
|
||
|
||
template < class _Derived, class _Record, class _Key,
|
||
class _BaseHashTable=CLKRHashTable,
|
||
class _BaseIterator=_BaseHashTable::CIterator
|
||
>
|
||
class CTypedHashTable : public _BaseHashTable
|
||
{
|
||
public:
|
||
// convenient aliases
|
||
typedef _Derived Derived;
|
||
typedef _Record Record;
|
||
typedef _Key Key;
|
||
typedef _BaseHashTable BaseHashTable;
|
||
typedef CTypedHashTable<_Derived, _Record, _Key,
|
||
_BaseHashTable, _BaseIterator>
|
||
HashTable;
|
||
typedef _BaseIterator BaseIterator;
|
||
|
||
// ApplyIf() and DeleteIf(): Does the record match the predicate?
|
||
// Note: takes a Record*, not a const Record*. You can modify the
|
||
// record in Pred() or Action(), if you like, but if you do, you
|
||
// should use LKL_WRITELOCK to lock the table.
|
||
typedef LK_PREDICATE (WINAPI *PFnRecordPred) (Record* pRec, void* pvState);
|
||
|
||
// Apply() et al: Perform action on record.
|
||
typedef LK_ACTION (WINAPI *PFnRecordAction)(Record* pRec, void* pvState);
|
||
|
||
private:
|
||
|
||
// Wrappers for the typesafe methods exposed by the derived class
|
||
|
||
static const DWORD_PTR
|
||
_ExtractKey(const void* pvRecord)
|
||
{
|
||
const _Record* pRec = static_cast<const _Record*>(pvRecord);
|
||
_Key key = static_cast<_Key>(_Derived::ExtractKey(pRec));
|
||
// I would prefer to use reinterpret_cast here, but the stupid
|
||
// Win64 compiler thinks it knows better than I do.
|
||
return (const DWORD_PTR) key;
|
||
}
|
||
|
||
static DWORD
|
||
_CalcKeyHash(const DWORD_PTR pnKey)
|
||
{
|
||
_Key key = LKRHASH_HACKY_CAST(_Key, pnKey);
|
||
return _Derived::CalcKeyHash(key);
|
||
}
|
||
|
||
static bool
|
||
_EqualKeys(const DWORD_PTR pnKey1, const DWORD_PTR pnKey2)
|
||
{
|
||
_Key key1 = LKRHASH_HACKY_CAST(_Key, pnKey1);
|
||
_Key key2 = LKRHASH_HACKY_CAST(_Key, pnKey2);
|
||
return _Derived::EqualKeys(key1, key2);
|
||
}
|
||
|
||
static void
|
||
_AddRefRecord(const void* pvRecord, int nIncr)
|
||
{
|
||
_Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
|
||
_Derived::AddRefRecord(pRec, nIncr);
|
||
}
|
||
|
||
|
||
// Typesafe wrappers for Apply, ApplyIf, and DeleteIf.
|
||
|
||
class CState
|
||
{
|
||
public:
|
||
PFnRecordPred m_pfnPred;
|
||
PFnRecordAction m_pfnAction;
|
||
void* m_pvState;
|
||
|
||
CState(
|
||
PFnRecordPred pfnPred,
|
||
PFnRecordAction pfnAction,
|
||
void* pvState)
|
||
: m_pfnPred(pfnPred), m_pfnAction(pfnAction), m_pvState(pvState)
|
||
{}
|
||
};
|
||
|
||
static LK_PREDICATE
|
||
_Pred(const void* pvRecord, void* pvState)
|
||
{
|
||
_Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
|
||
CState* pState = static_cast<CState*>(pvState);
|
||
|
||
return (*pState->m_pfnPred)(pRec, pState->m_pvState);
|
||
}
|
||
|
||
static LK_ACTION
|
||
_Action(const void* pvRecord, void* pvState)
|
||
{
|
||
_Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
|
||
CState* pState = static_cast<CState*>(pvState);
|
||
|
||
return (*pState->m_pfnAction)(pRec, pState->m_pvState);
|
||
}
|
||
|
||
public:
|
||
CTypedHashTable(
|
||
LPCSTR pszName, // An identifier for debugging
|
||
double maxload=LK_DFLT_MAXLOAD, // Upperbound on avg chain length
|
||
DWORD initsize=LK_DFLT_INITSIZE, // Initial size of hash table.
|
||
DWORD num_subtbls=LK_DFLT_NUM_SUBTBLS// #subordinate hash tables.
|
||
)
|
||
: _BaseHashTable(pszName, _ExtractKey, _CalcKeyHash, _EqualKeys,
|
||
_AddRefRecord, maxload, initsize, num_subtbls)
|
||
{}
|
||
|
||
LK_RETCODE InsertRecord(const _Record* pRec, bool fOverwrite=false)
|
||
{ return _BaseHashTable::InsertRecord(pRec, fOverwrite); }
|
||
|
||
LK_RETCODE DeleteKey(const _Key key)
|
||
{ return _BaseHashTable::DeleteKey(reinterpret_cast<const DWORD_PTR>(key));}
|
||
|
||
LK_RETCODE DeleteRecord(const _Record* pRec)
|
||
{ return _BaseHashTable::DeleteRecord(pRec);}
|
||
|
||
// Note: returns a _Record**, not a const Record**. Note that you
|
||
// can use a const type for the template parameter to ensure constness.
|
||
LK_RETCODE FindKey(const _Key key, _Record** ppRec) const
|
||
{
|
||
if (ppRec == NULL)
|
||
return LK_BAD_RECORD;
|
||
*ppRec = NULL;
|
||
const void* pvRec = NULL;
|
||
const void* pvKey = reinterpret_cast<const void*>((DWORD_PTR)(key));
|
||
DWORD_PTR pnKey = reinterpret_cast<DWORD_PTR>(pvKey);
|
||
LK_RETCODE lkrc = _BaseHashTable::FindKey(pnKey, &pvRec);
|
||
*ppRec = static_cast<_Record*>(const_cast<void*>(pvRec));
|
||
return lkrc;
|
||
}
|
||
|
||
LK_RETCODE FindRecord(const _Record* pRec) const
|
||
{ return _BaseHashTable::FindRecord(pRec);}
|
||
|
||
|
||
// Other C{Linear}HashTable methods can be exposed without change
|
||
// TODO: Print?
|
||
|
||
|
||
// Typesafe wrappers for Apply et al
|
||
|
||
DWORD Apply(PFnRecordAction pfnAction,
|
||
void* pvState=NULL,
|
||
LK_LOCKTYPE lkl=LKL_READLOCK)
|
||
{
|
||
IRTLASSERT(pfnAction != NULL);
|
||
if (pfnAction == NULL)
|
||
return 0;
|
||
|
||
CState state(NULL, pfnAction, pvState);
|
||
return _BaseHashTable::Apply(_Action, &state, lkl);
|
||
}
|
||
|
||
DWORD ApplyIf(PFnRecordPred pfnPredicate,
|
||
PFnRecordAction pfnAction,
|
||
void* pvState=NULL,
|
||
LK_LOCKTYPE lkl=LKL_READLOCK)
|
||
{
|
||
IRTLASSERT(pfnPredicate != NULL && pfnAction != NULL);
|
||
if (pfnPredicate == NULL || pfnAction == NULL)
|
||
return 0;
|
||
|
||
CState state(pfnPredicate, pfnAction, pvState);
|
||
return _BaseHashTable::ApplyIf(_Pred, _Action, &state, lkl);
|
||
}
|
||
|
||
DWORD DeleteIf(PFnRecordPred pfnPredicate, void* pvState=NULL)
|
||
{
|
||
IRTLASSERT(pfnPredicate != NULL);
|
||
if (pfnPredicate == NULL)
|
||
return 0;
|
||
|
||
CState state(pfnPredicate, NULL, pvState);
|
||
return _BaseHashTable::DeleteIf(_Pred, &state);
|
||
}
|
||
|
||
|
||
// Typesafe wrappers for iterators
|
||
|
||
|
||
class CIterator : public _BaseIterator
|
||
{
|
||
private:
|
||
// Private, unimplemented copy ctor and op= to prevent
|
||
// compiler synthesizing them.
|
||
CIterator(const CIterator&);
|
||
CIterator& operator=(const CIterator&);
|
||
|
||
public:
|
||
CIterator(
|
||
LK_LOCKTYPE lkl=LKL_WRITELOCK)
|
||
: _BaseIterator(lkl)
|
||
{}
|
||
|
||
_Record* Record() const
|
||
{
|
||
const _BaseIterator* pBase = static_cast<const _BaseIterator*>(this);
|
||
return reinterpret_cast<_Record*>(const_cast<void*>(
|
||
pBase->Record()));
|
||
}
|
||
|
||
_Key Key() const
|
||
{
|
||
const _BaseIterator* pBase = static_cast<const _BaseIterator*>(this);
|
||
return reinterpret_cast<_Key>(reinterpret_cast<void*>(pBase->Key()));
|
||
}
|
||
};
|
||
|
||
// readonly iterator
|
||
class CConstIterator : public CIterator
|
||
{
|
||
private:
|
||
// Private, unimplemented copy ctor and op= to prevent
|
||
// compiler synthesizing them.
|
||
CConstIterator(const CConstIterator&);
|
||
CConstIterator& operator=(const CConstIterator&);
|
||
|
||
public:
|
||
CConstIterator()
|
||
: CIterator(LKL_READLOCK)
|
||
{}
|
||
|
||
const _Record* Record() const
|
||
{
|
||
return CIterator::Record();
|
||
}
|
||
|
||
const _Key Key() const
|
||
{
|
||
return CIterator::Key();
|
||
}
|
||
};
|
||
|
||
|
||
public:
|
||
LK_RETCODE InitializeIterator(CIterator* piter)
|
||
{
|
||
return _BaseHashTable::InitializeIterator(piter);
|
||
}
|
||
|
||
LK_RETCODE IncrementIterator(CIterator* piter)
|
||
{
|
||
return _BaseHashTable::IncrementIterator(piter);
|
||
}
|
||
|
||
LK_RETCODE CloseIterator(CIterator* piter)
|
||
{
|
||
return _BaseHashTable::CloseIterator(piter);
|
||
}
|
||
|
||
LK_RETCODE InitializeIterator(CConstIterator* piter) const
|
||
{
|
||
return const_cast<HashTable*>(this)
|
||
->InitializeIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
LK_RETCODE IncrementIterator(CConstIterator* piter) const
|
||
{
|
||
return const_cast<HashTable*>(this)
|
||
->IncrementIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
LK_RETCODE CloseIterator(CConstIterator* piter) const
|
||
{
|
||
return const_cast<HashTable*>(this)
|
||
->CloseIterator(static_cast<CIterator*>(piter));
|
||
}
|
||
|
||
};
|
||
|
||
|
||
|
||
#ifdef __LKRHASH_NAMESPACE__
|
||
}
|
||
#endif // __LKRHASH_NAMESPACE__
|
||
|
||
|
||
#endif // __LKRHASH_H__
|
||
|
||
|
||
#ifdef SAMPLE_LKRHASH_TESTCLASS
|
||
|
||
|
||
//--------------------------------------------------------------------
|
||
// An example of how to create a wrapper for CLKRHashTable
|
||
//--------------------------------------------------------------------
|
||
|
||
// some random class
|
||
|
||
class CTest
|
||
{
|
||
public:
|
||
enum {BUFFSIZE=20};
|
||
|
||
int m_n; // This will also be a key
|
||
char m_sz[BUFFSIZE]; // This will be the primary key
|
||
bool m_fWhatever;
|
||
mutable LONG m_cRefs; // Reference count for lifetime management.
|
||
// Must be mutable to use 'const CTest*' in
|
||
// hashtables
|
||
|
||
CTest(int n, const char* psz, bool f)
|
||
: m_n(n), m_fWhatever(f), m_cRefs(0)
|
||
{
|
||
strncpy(m_sz, psz, BUFFSIZE-1);
|
||
m_sz[BUFFSIZE-1] = '\0';
|
||
}
|
||
|
||
~CTest()
|
||
{
|
||
IRTLASSERT(m_cRefs == 0);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
// A typed hash table of CTests, keyed on the string field. Case-insensitive.
|
||
|
||
class CStringTestHashTable
|
||
: public CTypedHashTable<CStringTestHashTable, const CTest, const char*>
|
||
{
|
||
public:
|
||
CStringTestHashTable()
|
||
: CTypedHashTable<CStringTestHashTable, const CTest,
|
||
const char*>("string", LK_DFLT_MAXLOAD,
|
||
LK_SMALL_TABLESIZE)
|
||
{}
|
||
|
||
static const char*
|
||
ExtractKey(const CTest* pTest)
|
||
{
|
||
return pTest->m_sz;
|
||
}
|
||
|
||
static DWORD
|
||
CalcKeyHash(const char* pszKey)
|
||
{
|
||
return HashStringNoCase(pszKey);
|
||
}
|
||
|
||
static bool
|
||
EqualKeys(const char* pszKey1, const char* pszKey2)
|
||
{
|
||
return _stricmp(pszKey1, pszKey2) == 0;
|
||
}
|
||
|
||
static void
|
||
AddRefRecord(const CTest* pTest, int nIncr)
|
||
{
|
||
if (nIncr == +1)
|
||
{
|
||
// or, perhaps, pIFoo->AddRef() (watch out for marshalling)
|
||
// or ++pTest->m_cRefs (single-threaded only)
|
||
InterlockedIncrement(&pTest->m_cRefs);
|
||
}
|
||
else if (nIncr == -1)
|
||
{
|
||
// or, perhaps, pIFoo->Release() or --pTest->m_cRefs;
|
||
LONG l = InterlockedDecrement(&pTest->m_cRefs);
|
||
|
||
// For some hashtables, it may also make sense to add the following
|
||
// if (l == 0) delete pTest;
|
||
// but that would typically only apply when InsertRecord was
|
||
// used thus
|
||
// lkrc = ht.InsertRecord(new CTest(foo, bar));
|
||
}
|
||
else
|
||
IRTLASSERT(0);
|
||
|
||
TRACE("AddRef(%p, %s) %d, cRefs == %d\n",
|
||
pTest, pTest->m_sz, nIncr, pTest->m_cRefs);
|
||
}
|
||
};
|
||
|
||
|
||
// Another typed hash table of CTests. This one is keyed on the numeric field.
|
||
|
||
class CNumberTestHashTable
|
||
: public CTypedHashTable<CNumberTestHashTable, const CTest, int>
|
||
{
|
||
public:
|
||
CNumberTestHashTable()
|
||
: CTypedHashTable<CNumberTestHashTable, const CTest, int>("number") {}
|
||
static int ExtractKey(const CTest* pTest) {return pTest->m_n;}
|
||
static DWORD CalcKeyHash(int nKey) {return Hash(nKey);}
|
||
static bool EqualKeys(int nKey1, int nKey2) {return nKey1 == nKey2;}
|
||
static void AddRefRecord(const CTest* pTest, int nIncr) {/* do nothing*/}
|
||
};
|
||
|
||
|
||
// A class to exercise ApplyIf()
|
||
|
||
class CApplyIfTest
|
||
{
|
||
public:
|
||
static LK_PREDICATE
|
||
Predicate(const CTest* pTest, void* pvState)
|
||
{
|
||
CApplyIfTest* pThis = static_cast<CApplyIfTest*>(pvState);
|
||
++pThis->m_cPreds;
|
||
TRACE("CApplyIfTest::Predicate(%p (%s, %d), %p)\n",
|
||
pTest, pTest->m_sz, pTest->m_n, pThis);
|
||
return (pTest->m_n % 10 == 7) ? LKP_PERFORM : LKP_NO_ACTION;
|
||
}
|
||
|
||
static LK_ACTION
|
||
Action(const CTest* pTest, void* pvState)
|
||
{
|
||
CApplyIfTest* pThis = static_cast<CApplyIfTest*>(pvState);
|
||
++pThis->m_cActions;
|
||
LK_ACTION lka = (pTest->m_n > 30) ? LKA_SUCCEEDED : LKA_FAILED;
|
||
|
||
TRACE("CApplyIfTest::Action(%p (%s, %d), %p) %s\n",
|
||
pTest, pTest->m_sz, pTest->m_n, pThis,
|
||
lka == LKA_SUCCEEDED ? "succeeded" : "failed");
|
||
|
||
if (lka == LKA_SUCCEEDED)
|
||
++pThis->m_cSuccesses;
|
||
else if (lka == LKA_FAILED)
|
||
++pThis->m_cFailures;
|
||
|
||
return lka;
|
||
}
|
||
|
||
int m_cPreds;
|
||
int m_cActions;
|
||
int m_cSuccesses;
|
||
int m_cFailures;
|
||
|
||
CApplyIfTest()
|
||
: m_cPreds(0), m_cActions(0), m_cSuccesses(0), m_cFailures(0)
|
||
{}
|
||
};
|
||
|
||
|
||
// The Predicate and Action functions can be static member functions,
|
||
// but don't have to be
|
||
|
||
LK_PREDICATE
|
||
DeleteIfGt10(
|
||
const CTest* pTest,
|
||
void* pvState)
|
||
{
|
||
TRACE("DeleteIfGt10(%p, %s, %p) = %d\n",
|
||
pTest, pTest->m_sz, pvState, pTest->m_n);
|
||
return (pTest->m_n > 10) ? LKP_PERFORM : LKP_NO_ACTION;
|
||
}
|
||
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
|
||
void Test(
|
||
bool fVerbose)
|
||
{
|
||
// Some objects for the hash tables
|
||
CTest tl(5, "Larson", true);
|
||
CTest tk(17, "Krishnan", false);
|
||
CTest tr(37, "Reilly", true);
|
||
|
||
// A string-keyed hash table
|
||
CStringTestHashTable stht;
|
||
|
||
IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tl));
|
||
IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tk));
|
||
IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tr));
|
||
|
||
TRACE("Check the overwrite feature of InsertRecord\n");
|
||
IRTLVERIFY(LK_KEY_EXISTS == stht.InsertRecord(&tr, false));
|
||
IRTLASSERT(tr.m_cRefs == 1);
|
||
IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tr, true));
|
||
IRTLASSERT(tr.m_cRefs == 1); // 1+1-1 == 1
|
||
|
||
TRACE("Check that the keys are really present in the table and that "
|
||
"the refcounting works\n");
|
||
const CTest* pTest = NULL;
|
||
IRTLVERIFY(LK_SUCCESS == stht.FindKey(tl.m_sz, &pTest) && pTest == &tl);
|
||
IRTLASSERT(tl.m_cRefs == 2);
|
||
IRTLVERIFY(LK_SUCCESS == stht.FindKey(tk.m_sz, &pTest) && pTest == &tk);
|
||
IRTLASSERT(tk.m_cRefs == 2);
|
||
IRTLVERIFY(LK_SUCCESS == stht.FindKey(tr.m_sz, &pTest) && pTest == &tr);
|
||
IRTLASSERT(tr.m_cRefs == 2);
|
||
IRTLVERIFY(LK_SUCCESS == stht.FindRecord(&tr));
|
||
IRTLASSERT(tr.m_cRefs == 2); // FindRecord does not addref
|
||
|
||
TRACE("Look for a key under an alternate spelling (case-insensitive)\n");
|
||
IRTLVERIFY(LK_SUCCESS == stht.FindKey("rEiLlY", &pTest) && pTest == &tr);
|
||
IRTLASSERT(tr.m_cRefs == 3);
|
||
|
||
TRACE("Release the references added by FindKey\n");
|
||
stht.AddRefRecord(&tl, -1);
|
||
tk.m_cRefs--;
|
||
tr.m_cRefs = 1;
|
||
|
||
TRACE("Now build the numeric hash table\n");
|
||
CNumberTestHashTable ntht;
|
||
|
||
IRTLVERIFY(LK_SUCCESS == ntht.InsertRecord(&tl));
|
||
IRTLVERIFY(LK_SUCCESS == ntht.InsertRecord(&tk));
|
||
IRTLVERIFY(LK_SUCCESS == ntht.InsertRecord(&tr));
|
||
|
||
TRACE("Test ApplyIf()\n");
|
||
CApplyIfTest ait;
|
||
|
||
IRTLVERIFY(1 == ntht.ApplyIf(ait.Predicate, ait.Action, &ait));
|
||
IRTLASSERT(3 == ait.m_cPreds && 2 == ait.m_cActions
|
||
&& 1 == ait.m_cSuccesses && 1 == ait.m_cFailures);
|
||
|
||
TRACE("Test DeleteIf()\n");
|
||
IRTLASSERT(3 == ntht.Size());
|
||
ntht.DeleteIf(DeleteIfGt10, NULL);
|
||
IRTLASSERT(1 == ntht.Size());
|
||
|
||
TRACE("Check that the keys that were supposed to be deleted "
|
||
"really are gone\n");
|
||
IRTLASSERT(tl.m_n <= 10);
|
||
IRTLVERIFY(LK_SUCCESS == ntht.FindKey(tl.m_n, &pTest) && pTest == &tl);
|
||
IRTLASSERT(tk.m_n > 10);
|
||
IRTLVERIFY(LK_NO_SUCH_KEY == ntht.FindKey(tk.m_n, &pTest)
|
||
&& pTest == NULL);
|
||
IRTLASSERT(tr.m_n > 10);
|
||
IRTLVERIFY(LK_NO_SUCH_KEY == ntht.FindKey(tr.m_n, &pTest)
|
||
&& pTest == NULL);
|
||
|
||
IRTLVERIFY(LK_SUCCESS == ntht.DeleteRecord(&tl));
|
||
IRTLASSERT(0 == ntht.Size());
|
||
|
||
TRACE("Check Iterators\n");
|
||
DWORD cRec = 0;
|
||
CStringTestHashTable::CIterator iter;
|
||
LK_RETCODE lkrc = stht.InitializeIterator(&iter);
|
||
|
||
while (lkrc == LK_SUCCESS)
|
||
{
|
||
++cRec;
|
||
CStringTestHashTable::Key pszKey = iter.Key();
|
||
CStringTestHashTable::Record* pRec = iter.Record();
|
||
|
||
IRTLASSERT(pRec == &tl || pRec == &tk || pRec == &tr);
|
||
if (fVerbose)
|
||
printf("Record(%p) contains \"%s\"\n", pRec, pszKey);
|
||
lkrc = stht.IncrementIterator(&iter);
|
||
}
|
||
|
||
IRTLASSERT(lkrc == LK_NO_MORE_ELEMENTS);
|
||
|
||
lkrc = stht.CloseIterator(&iter);
|
||
IRTLASSERT(lkrc == LK_SUCCESS);
|
||
IRTLASSERT(cRec == stht.Size());
|
||
|
||
TRACE("Check const iterators\n");
|
||
const CStringTestHashTable& sthtConst = stht;
|
||
CStringTestHashTable::CConstIterator iterConst;
|
||
cRec = 0;
|
||
|
||
lkrc = sthtConst.InitializeIterator(&iterConst);
|
||
|
||
while (lkrc == LK_SUCCESS)
|
||
{
|
||
++cRec;
|
||
const CStringTestHashTable::Key pszKey = iterConst.Key();
|
||
const CStringTestHashTable::Record* pRec = iterConst.Record();
|
||
|
||
IRTLASSERT(pRec == &tl || pRec == &tk || pRec == &tr);
|
||
if (fVerbose)
|
||
printf("Const Record(%p) contains \"%s\"\n", pRec, pszKey);
|
||
lkrc = sthtConst.IncrementIterator(&iterConst);
|
||
}
|
||
|
||
IRTLASSERT(lkrc == LK_NO_MORE_ELEMENTS);
|
||
|
||
lkrc = sthtConst.CloseIterator(&iterConst);
|
||
IRTLASSERT(lkrc == LK_SUCCESS);
|
||
IRTLASSERT(cRec == sthtConst.Size());
|
||
|
||
#if 1
|
||
TRACE("Check Clear\n");
|
||
stht.Clear();
|
||
IRTLASSERT(0 == stht.Size());
|
||
#else
|
||
TRACE("Check DeleteKey\n");
|
||
IRTLVERIFY(LK_SUCCESS == stht.DeleteKey(tl.m_sz));
|
||
IRTLVERIFY(LK_SUCCESS == stht.DeleteKey(tk.m_sz));
|
||
IRTLVERIFY(LK_SUCCESS == stht.DeleteKey(tr.m_sz));
|
||
#endif
|
||
|
||
TRACE("Test done\n");
|
||
// ~CTest will check for m_cRefs==0
|
||
}
|
||
|
||
#endif // SAMPLE_LKRHASH_TESTCLASS
|