Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2734 lines
91 KiB

  1. /*++
  2. Copyright (c) 1998-2001 Microsoft Corporation
  3. Module Name :
  4. LKRhash.h
  5. Abstract:
  6. Declares LKRhash: a fast, scalable, cache- and MP-friendly hash table
  7. Deprecated: Use the version in %sdxroot%\inetsrv\iis\iisrearc\core\inc
  8. instead.
  9. Author:
  10. Paul (Per-Ake) Larson, palarson@microsoft.com, July 1997
  11. Murali R. Krishnan (MuraliK)
  12. George V. Reilly (GeorgeRe) 06-Jan-1998
  13. Environment:
  14. Win32 - User Mode
  15. Project:
  16. Internet Information Server RunTime Library
  17. Revision History:
  18. 10/01/1998 - Change name from LKhash to LKRhash
  19. --*/
  20. #define LKR_COMPACT_DELETE 1
  21. #define LKR_NEWCODE 1
  22. // #define LKR_FIND_FIRST 1
  23. #define LKR_SUBTABLE 1
  24. #define LKR_MASK 1
  25. #ifndef __LKRHASH_H__
  26. #define __LKRHASH_H__
  27. //=====================================================================
  28. // The class CLKRLinearHashTable defined in this file provides dynamic hash
  29. // tables, i.e. tables that grow and shrink dynamically with
  30. // the number of records in the table.
  31. // The basic method used is linear hashing, as explained in:
  32. //
  33. // P.-A. Larson, Dynamic Hash Tables, Comm. of the ACM, 31, 4 (1988)
  34. //
  35. // This version has the following characteristics:
  36. // - It is thread-safe and uses spin locks for synchronization.
  37. // - It was designed to support very high rates of concurrent
  38. // operations (inserts/deletes/lookups). It achieves this by
  39. // (a) partitioning a CLKRHashTable into a collection of
  40. // CLKRLinearHashTables to reduce contention on the global table lock.
  41. // (b) minimizing the hold time on a table lock, preferring to lock
  42. // down a bucket chain instead.
  43. // - The design is L1 cache-conscious. See CNodeClump.
  44. // - It is designed for sets varying in size from a dozen
  45. // elements to several million.
  46. //
  47. // Main classes:
  48. // CLKRLinearHashTable: thread-safe linear hash table
  49. // CLKRHashTable: collection of CLKRLinearHashTables
  50. // CTypedHashTable: typesafe wrapper for CLKRHashTable
  51. //
  52. //
  53. // Paul Larson, [email protected], July 1997
  54. // Original implementation with input from Murali R. Krishnan,
  55. // [email protected].
  56. //
  57. // George V. Reilly, [email protected], Dec 1997-Jan 1998
  58. // Massive cleanup and rewrite. Added templates.
  59. //=====================================================================
  60. // 1) Linear Hashing
  61. // ------------------
  62. //
  63. // Linear hash tables grow and shrink dynamically with the number of
  64. // records in the table. The growth or shrinkage is smooth: logically,
  65. // one bucket at a time but physically in larger increments
  66. // (64 buckets). An insertion (deletion) may cause an expansion
  67. // (contraction) of the table. This causes relocation of a small number
  68. // of records (at most one bucket worth). All operations (insert,
  69. // delete, lookup) take constant expected time, regardless of the
  70. // current size or the growth of the table.
  71. //
  72. // 2) LKR extensions to Linear hash table
  73. // --------------------------------------
  74. //
  75. // Larson-Krishnan-Reilly extensions to Linear hash tables for multiprocessor
  76. // scalability and improved cache performance.
  77. //
  78. // Traditional implementations of linear hash tables use one global lock
  79. // to prevent interference between concurrent operations
  80. // (insert/delete/lookup) on the table. The single lock easily becomes
  81. // the bottleneck in SMP scenarios when multiple threads are used.
  82. //
  83. // Traditionally, a (hash) bucket is implemented as a chain of
  84. // single-item nodes. Every operation results in chasing down a chain
  85. // looking for an item. However, pointer chasing is very slow on modern
  86. // systems because almost every jump results in a cache miss. L2 (or L3)
  87. // cache misses are very expensive in missed CPU cycles and the cost is
  88. // increasing (going to 100s of cycles in the future).
  89. //
  90. // LKR extensions offer
  91. // 1) Partitioning (by hashing) of records among multiple subtables.
  92. // Each subtable has locks but there is no global lock. Each
  93. // subtable receives a much lower rate of operations, resulting in
  94. // fewer conflicts.
  95. //
  96. // 2) Improved cache locality by grouping keys and their hash values
  97. // into contigous chunks that fit exactly into one (or a few)
  98. // cache lines.
  99. //
  100. // Specifically the implementation that exists here achieves this using
  101. // the following techniques.
  102. //
  103. // Class CLKRHashTable is the top-level data structure that dynamically
  104. // creates m_cSubTables linear hash tables. The CLKRLinearHashTables act as
  105. // the subtables to which items and accesses are fanned out. A good
  106. // hash function multiplexes requests uniformly to various subtables,
  107. // thus minimizing traffic to any single subtable. The implemenation
  108. // uses a home-grown version of bounded spinlocks, that is, a thread
  109. // does not spin on a lock indefinitely, instead yielding after a
  110. // predetermined number of loops.
  111. //
  112. // Each CLKRLinearHashTable consists of a CDirEntry pointing to segments
  113. // each holding m_dwSegSize CBuckets. Each CBucket in turn consists of a
  114. // chain of CNodeClumps. Each CNodeClump contains a group of
  115. // NODES_PER_CLUMP hash values (aka hash keys or signatures) and
  116. // pointers to the associated data items. Keeping the signatures
  117. // together increases the cache locality in scans for lookup.
  118. //
  119. // Traditionally, people store a link-list element right inside the
  120. // object that is hashed and use this link-list for the chaining of data
  121. // blocks. However, keeping just the pointers to the data object and
  122. // not chaining through them limits the need for bringing in the data
  123. // object to the cache. We need to access the data object only if the
  124. // hash values match. This limits the cache-thrashing behaviour
  125. // exhibited by conventional implementations. It has the additional
  126. // benefit that the objects themselves do not need to be modified
  127. // in order to be collected in the hash table (i.e., it's non-invasive).
  128. //--------------------------------------------------------------------
  129. // TODO
  130. // * Debugging support for iisprobe and inetdbg?
  131. // * Use auto_ptrs.
  132. // * Provide ReplaceRecord and DeleteRecord methods on iterators.
  133. // * Sloppy iterators
  134. // * Provide implementations of the STL collection classes, map, set,
  135. // multimap, and multiset.
  136. // * Make exception safe.
  137. //--------------------------------------------------------------------
  138. #include <irtldbg.h>
  139. #include <lstentry.h>
  140. #include <hashfn.h>
  141. #include <limits.h>
  142. #ifdef __LKRHASH_NAMESPACE__
  143. namespace LKRHash {
  144. #endif // __LKRHASH_NAMESPACE__
  145. enum LK_TABLESIZE {
  146. LK_SMALL_TABLESIZE= 1, // < 200 elements
  147. LK_MEDIUM_TABLESIZE= 2, // 200...10,000 elements
  148. LK_LARGE_TABLESIZE= 3, // 10,000+ elements
  149. };
  150. // Default values for the hashtable constructors
  151. enum {
  152. LK_DFLT_MAXLOAD= 4, // Default upperbound on average chain length.
  153. LK_DFLT_INITSIZE=LK_MEDIUM_TABLESIZE, // Default initial size of hash table
  154. LK_DFLT_NUM_SUBTBLS= 0, // Use a heuristic to choose #subtables
  155. };
  156. // build fix hack
  157. enum {
  158. DFLT_LK_MAXLOAD= LK_DFLT_MAXLOAD,
  159. DFLT_LK_INITSIZE= LK_DFLT_INITSIZE,
  160. DFLT_LK_NUM_SUBTBLS= LK_DFLT_NUM_SUBTBLS,
  161. };
  162. //--------------------------------------------------------------------
  163. // forward declarations
  164. class IRTL_DLLEXP CLKRLinearHashTable;
  165. class IRTL_DLLEXP CLKRHashTable;
  166. template <class _Der, class _Rcd, class _Ky, class _HT, class _Iter>
  167. class CTypedHashTable;
  168. //--------------------------------------------------------------------
  169. // Possible return codes from public member functions of
  170. // CLKRLinearHashTable, CLKRHashTable, and CTypedHashTable
  171. enum LK_RETCODE {
  172. // severe errors < 0
  173. LK_UNUSABLE = -99, // Table corrupted: all bets are off
  174. LK_ALLOC_FAIL, // ran out of memory
  175. LK_BAD_ITERATOR, // invalid iterator; e.g., points to another table
  176. LK_BAD_RECORD, // invalid record; e.g., NULL for InsertRecord
  177. LK_BAD_PARAMETERS, // invalid parameters; e.g., NULL fnptrs to ctor
  178. LK_NOT_INITIALIZED, // LKRHashTableInit was not called
  179. LK_SUCCESS = 0, // everything's okay
  180. LK_KEY_EXISTS, // key already present for InsertRecord(no-overwrite)
  181. LK_NO_SUCH_KEY, // key not found
  182. LK_NO_MORE_ELEMENTS,// iterator exhausted
  183. };
  184. #define LK_SUCCEEDED(lkrc) ((lkrc) >= LK_SUCCESS)
  185. //--------------------------------------------------------------------
  186. // Return codes from PFnRecordPred.
  187. enum LK_PREDICATE {
  188. LKP_ABORT = 1, // Stop walking the table immediately
  189. LKP_NO_ACTION = 2, // No action, just keep walking
  190. LKP_PERFORM = 3, // Perform action and continue walking
  191. LKP_PERFORM_STOP = 4, // Perform action, then stop
  192. LKP_DELETE = 5, // Delete record and keep walking
  193. LKP_DELETE_STOP = 6, // Delete record, then stop
  194. };
  195. //--------------------------------------------------------------------
  196. // Return codes from PFnRecordAction.
  197. enum LK_ACTION {
  198. LKA_ABORT = 1, // Stop walking the table immediately
  199. LKA_FAILED = 2, // Action failed; continue walking the table
  200. LKA_SUCCEEDED = 3, // Action succeeded; continue walking the table
  201. };
  202. //--------------------------------------------------------------------
  203. // Parameter to Apply and ApplyIf.
  204. enum LK_LOCKTYPE {
  205. LKL_READLOCK = 1, // Lock the table for reading (for constness)
  206. LKL_WRITELOCK = 2, // Lock the table for writing
  207. };
  208. //--------------------------------------------------------------------
  209. // Global table lock code. This is only used to measure how much a
  210. // slowdown having a global lock on the CLKRHashTable causes. It is *never*
  211. // used in production code.
  212. // #define LKRHASH_GLOBAL_LOCK CCritSec
  213. #ifdef LKRHASH_GLOBAL_LOCK
  214. # define LKRHASH_GLOBAL_LOCK_DECLARATIONS() \
  215. typedef LKRHASH_GLOBAL_LOCK GlobalLock; \
  216. mutable GlobalLock m_lkGlobal;
  217. # define LKRHASH_GLOBAL_READ_LOCK() m_lkGlobal.ReadLock()
  218. # define LKRHASH_GLOBAL_WRITE_LOCK() m_lkGlobal.WriteLock()
  219. # define LKRHASH_GLOBAL_READ_UNLOCK() m_lkGlobal.ReadUnlock()
  220. # define LKRHASH_GLOBAL_WRITE_UNLOCK() m_lkGlobal.WriteUnlock()
  221. #else // !LKRHASH_GLOBAL_LOCK
  222. # define LKRHASH_GLOBAL_LOCK_DECLARATIONS()
  223. // These ones will be optimized away by the compiler
  224. # define LKRHASH_GLOBAL_READ_LOCK() ((void)0)
  225. # define LKRHASH_GLOBAL_WRITE_LOCK() ((void)0)
  226. # define LKRHASH_GLOBAL_READ_UNLOCK() ((void)0)
  227. # define LKRHASH_GLOBAL_WRITE_UNLOCK() ((void)0)
  228. #endif // !LKRHASH_GLOBAL_LOCK
  229. //--------------------------------------------------------------------
  230. // Statistical information returned by GetStatistics
  231. //--------------------------------------------------------------------
  232. #ifdef LOCK_INSTRUMENTATION
  233. class IRTL_DLLEXP CAveragedLockStats : public CLockStatistics
  234. {
  235. public:
  236. int m_nItems;
  237. CAveragedLockStats()
  238. : m_nItems(1)
  239. {}
  240. };
  241. #endif // LOCK_INSTRUMENTATION
  242. class IRTL_DLLEXP CLKRHashTableStats
  243. {
  244. public:
  245. int RecordCount; // number of records in the table
  246. int TableSize; // table size in number of slots
  247. int DirectorySize; // number of entries in directory
  248. int LongestChain; // longest hash chain in the table
  249. int EmptySlots; // number of unused hash slots
  250. double SplitFactor; // fraction of buckets split
  251. double AvgSearchLength; // average length of a successful search
  252. double ExpSearchLength; // theoretically expected length
  253. double AvgUSearchLength; // average length of an unsuccessful search
  254. double ExpUSearchLength; // theoretically expected length
  255. int NodeClumpSize; // number of slots in a node clump
  256. int CBucketSize; // sizeof(CBucket)
  257. #ifdef LOCK_INSTRUMENTATION
  258. CAveragedLockStats m_alsTable; // stats for table lock
  259. CAveragedLockStats m_alsBucketsAvg; // avg of stats for bucket locks
  260. CGlobalLockStatistics m_gls; // global statistics for all locks
  261. #endif // LOCK_INSTRUMENTATION
  262. enum {
  263. MAX_BUCKETS = 40,
  264. };
  265. // histogram of bucket lengths
  266. LONG m_aBucketLenHistogram[MAX_BUCKETS];
  267. CLKRHashTableStats()
  268. : RecordCount(0),
  269. TableSize(0),
  270. DirectorySize(0),
  271. LongestChain(0),
  272. EmptySlots(0),
  273. SplitFactor(0.0),
  274. AvgSearchLength(0.0),
  275. ExpSearchLength(0.0),
  276. AvgUSearchLength(0.0),
  277. ExpUSearchLength(0.0),
  278. NodeClumpSize(1),
  279. CBucketSize(0)
  280. {
  281. for (int i = MAX_BUCKETS; --i >= 0; )
  282. m_aBucketLenHistogram[i] = 0;
  283. }
  284. static const LONG*
  285. BucketSizes()
  286. {
  287. static const LONG s_aBucketSizes[MAX_BUCKETS] = {
  288. 1, 2, 3, 4, 5, 6, 7, 8, 9,
  289. 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
  290. 20, 21, 22, 23, 24, 25, 30, 40, 50, 60,
  291. 70, 80, 90, 100, 200, 500, 1000,10000, 100000, LONG_MAX,
  292. };
  293. return s_aBucketSizes;
  294. }
  295. static LONG
  296. BucketSize(
  297. LONG nBucketIndex)
  298. {
  299. IRTLASSERT(0 <= nBucketIndex && nBucketIndex < MAX_BUCKETS);
  300. return BucketSizes()[nBucketIndex];
  301. }
  302. static LONG
  303. BucketIndex(
  304. LONG nBucketLength)
  305. {
  306. const LONG* palBucketSizes = BucketSizes();
  307. LONG i = 0;
  308. while (palBucketSizes[i] < nBucketLength)
  309. ++i;
  310. if (i == MAX_BUCKETS || palBucketSizes[i] > nBucketLength)
  311. --i;
  312. IRTLASSERT(0 <= i && i < MAX_BUCKETS);
  313. return i;
  314. }
  315. };
  316. //--------------------------------------------------------------------
  317. // CLKRLinearHashTable deals with void* records. These typedefs
  318. // provide prototypes for functions that manipulate instances of
  319. // those records. CTypedHashTable and CStringTestHashTable (below) show a
  320. // way to encapsulate these in typesafe wrappers.
  321. //--------------------------------------------------------------------
  322. // Given a record, return its key. Assumes that the key is embedded in
  323. // the record, or at least somehow derivable from the record. For
  324. // completely unrelated keys & values, a wrapper class should use
  325. // something like STL's pair<key,value> template to aggregate them
  326. // into a record.
  327. typedef const DWORD_PTR (WINAPI *PFnExtractKey) (const void* pvRecord);
  328. // Given a key, return its hash signature. The hashing functions in
  329. // hashfn.h (or something that builds upon them) are suggested.
  330. typedef DWORD (WINAPI *PFnCalcKeyHash) (const DWORD_PTR pnKey);
  331. // Compare two keys for equality; e.g., _stricmp, memcmp, operator==
  332. typedef bool (WINAPI *PFnEqualKeys) (const DWORD_PTR pnKey1,
  333. const DWORD_PTR pnKey2);
  334. // Increment the reference count of a record before returning it from
  335. // FindKey. It's necessary to do it in FindKey itself while the bucket
  336. // is still locked, rather than one of the wrappers, to avoid race
  337. // conditions. Similarly, the reference count is incremented in
  338. // InsertRecord and decremented in DeleteKey. Finally, if an old record
  339. // is overwritten in InsertRecord, its reference count is decremented.
  340. //
  341. // It's up to you to decrement the reference count when you're finished
  342. // with it after retrieving it via FindKey and to determine the
  343. // semantics of what this means. The hashtable itself has no notion of
  344. // reference counts; this is merely to help with the lifetime management
  345. // of the record objects.
  346. typedef void (WINAPI *PFnAddRefRecord)(const void* pvRecord, int nIncr);
  347. // ApplyIf() and DeleteIf(): Does the record match the predicate?
  348. typedef LK_PREDICATE (WINAPI *PFnRecordPred) (const void* pvRecord,
  349. void* pvState);
  350. // Apply() et al: Perform action on record.
  351. typedef LK_ACTION (WINAPI *PFnRecordAction)(const void* pvRecord,
  352. void* pvState);
  353. //--------------------------------------------------------------------
  354. // Custom memory allocators
  355. //--------------------------------------------------------------------
  356. #define LKRHASH_ACACHE 1
  357. // #define LKRHASH_MANODEL 1
  358. // #define LKRHASH_MADEL 1
  359. // #define LKRHASH_MEM_DEFAULT_ALIGN 32
  360. #ifndef LKRHASH_MEM_DEFAULT_ALIGN
  361. # define LKRHASH_MEM_DEFAULT_ALIGN 8
  362. #endif
  363. #if defined(LKRHASH_ACACHE)
  364. # include <acache.hxx>
  365. typedef ALLOC_CACHE_HANDLER CLKRhashAllocator;
  366. # define LKRHASH_ALLOCATOR_NEW(C, N) \
  367. const ALLOC_CACHE_CONFIGURATION acc = { 1, N, sizeof(C) }; \
  368. C::sm_palloc = new ALLOC_CACHE_HANDLER("IISRTL:" #C, &acc);
  369. #elif defined(LKRHASH_MANODEL)
  370. # include <manodel.hxx>
  371. typedef MEMORY_ALLOC_NO_DELETE CLKRhashAllocator;
  372. # define LKRHASH_ALLOCATOR_NEW(C, N) \
  373. C::sm_palloc = new MEMORY_ALLOC_NO_DELETE(sizeof(C), \
  374. LKRHASH_MEM_DEFAULT_ALIGN);
  375. #elif defined(LKRHASH_MADEL)
  376. # include <madel.hxx>
  377. typedef MEMORY_ALLOC_DELETE CLKRhashAllocator;
  378. # define LKRHASH_ALLOCATOR_NEW(C, N) \
  379. C::sm_palloc = new MEMORY_ALLOC_DELETE(sizeof(C), \
  380. LKRHASH_MEM_DEFAULT_ALIGN, N);
  381. #else // no custom allocator
  382. # undef LKRHASH_ALLOCATOR_NEW
  383. #endif // no custom allocator
  384. // Used to initialize and destroy custom allocators
  385. bool LKRHashTableInit();
  386. void LKRHashTableUninit();
  387. #ifdef LKRHASH_ALLOCATOR_NEW
  388. // placed inline in the declaration of class C
  389. # define LKRHASH_ALLOCATOR_DEFINITIONS(C) \
  390. protected: \
  391. static CLKRhashAllocator* sm_palloc; \
  392. friend bool LKRHashTableInit(); \
  393. friend void LKRHashTableUninit(); \
  394. public: \
  395. static void* operator new(size_t s) \
  396. { \
  397. IRTLASSERT(s == sizeof(C)); \
  398. IRTLASSERT(sm_palloc != NULL); \
  399. return sm_palloc->Alloc(); \
  400. } \
  401. static void operator delete(void* pv) \
  402. { \
  403. IRTLASSERT(pv != NULL); \
  404. IRTLASSERT(sm_palloc != NULL); \
  405. sm_palloc->Free(pv); \
  406. }
  407. // used in LKRHashTableInit()
  408. # define LKRHASH_ALLOCATOR_INIT(C, N, f) \
  409. { \
  410. if (f) \
  411. { \
  412. IRTLASSERT(C::sm_palloc == NULL); \
  413. LKRHASH_ALLOCATOR_NEW(C, N); \
  414. f = (C::sm_palloc != NULL); \
  415. } \
  416. }
  417. // used in LKRHashTableUninit()
  418. # define LKRHASH_ALLOCATOR_UNINIT(C) \
  419. { \
  420. if (C::sm_palloc != NULL) \
  421. { \
  422. delete C::sm_palloc; \
  423. C::sm_palloc = NULL; \
  424. } \
  425. }
  426. #else // !LKRHASH_ALLOCATOR_NEW
  427. # define LKRHASH_ALLOCATOR_DEFINITIONS(C)
  428. # define LKRHASH_ALLOCATOR_INIT(C, N, f)
  429. # define LKRHASH_ALLOCATOR_UNINIT(C)
  430. #endif // !LKRHASH_ALLOCATOR_NEW
  431. //--------------------------------------------------------------------
  432. // CLKRLinearHashTable
  433. //
  434. // A thread-safe linear hash table.
  435. //--------------------------------------------------------------------
  436. class IRTL_DLLEXP CLKRLinearHashTable
  437. {
  438. public:
  439. // typedef CSmallSpinLock TableLock;
  440. // typedef CFakeLock TableLock;
  441. // typedef CSpinLock TableLock;
  442. // typedef CSpinLock2 TableLock;
  443. // typedef CSpinLock3 TableLock;
  444. // typedef CShareLock TableLock;
  445. // typedef CReaderWriterLock TableLock;
  446. // typedef CReaderWriterLock2 TableLock;
  447. typedef CReaderWriterLock3 TableLock;
  448. typedef CSmallSpinLock BucketLock;
  449. // typedef CFakeLock BucketLock;
  450. // typedef CSpinLock BucketLock;
  451. // typedef CSpinLock2 BucketLock;
  452. // typedef CSpinLock3 BucketLock;
  453. // typedef CShareLock BucketLock;
  454. // typedef CReaderWriterLock BucketLock;
  455. // typedef CReaderWriterLock2 BucketLock;
  456. // typedef CReaderWriterLock3 BucketLock;
  457. class CIterator;
  458. friend class CLKRLinearHashTable::CIterator;
  459. private:
  460. class CNodeClump;
  461. friend class CLKRLinearHashTable::CNodeClump;
  462. friend class CLKRHashTable;
  463. #ifdef LKRHASH_ALLOCATOR_NEW
  464. friend bool LKRHashTableInit();
  465. friend void LKRHashTableUninit();
  466. #endif // LKRHASH_ALLOCATOR_NEW
  467. #ifdef LKRHASH_INSTRUMENTATION
  468. // TODO
  469. #endif // LKRHASH_INSTRUMENTATION
  470. enum {
  471. // Given M = A % B, A and B unsigned 32-bit integers greater than zero,
  472. // there are no values of A or B which yield M = 2^32-1. Why? Because
  473. // M must be less than B.
  474. HASH_INVALID_SIGNATURE = ULONG_MAX,
  475. };
  476. // Class for nodes on a bucket chain. Instead of a node containing
  477. // one (signature, record-pointer, next-tuple-pointer) tuple, it
  478. // contains _N_ such tuples. (N-1 next-tuple-pointers are omitted.)
  479. // This improves locality of reference greatly; i.e., it's L1
  480. // cache-friendly. It also reduces memory fragmentation and memory
  481. // allocator overhead. It does complicate the chain traversal code
  482. // slightly, admittedly.
  483. //
  484. // This theory is beautiful. In practice, however, CNodeClumps
  485. // are *not* perfectly aligned on 32-byte boundaries by the memory
  486. // allocators. Experimental results indicate that we get a 2-3%
  487. // speed improvement by using 32-byte-aligned blocks, but this must
  488. // be considered against the average of 16 bytes wasted per block.
  489. class CNodeClump
  490. {
  491. public:
  492. // Record slots per chunk - set so a chunk matches (one or
  493. // two) cache lines. 2 ==> 28 bytes, 6 ==> 60 bytes
  494. // Note: the default max load factor is 4.0, which implies that
  495. // there will seldom be more than one node clump in a chain.
  496. enum {
  497. BUCKET_BYTE_SIZE = 64,
  498. BUCKET_OVERHEAD = sizeof(BucketLock) + sizeof(CNodeClump*),
  499. NODE_SIZE = sizeof(const void*) + sizeof(DWORD),
  500. NODES_PER_CLUMP = (BUCKET_BYTE_SIZE - BUCKET_OVERHEAD) / NODE_SIZE
  501. };
  502. DWORD m_dwKeySigs[NODES_PER_CLUMP]; // hash values computed from keys
  503. CNodeClump* m_pncNext; // next node clump on the chain
  504. const void* m_pvNode[NODES_PER_CLUMP];// pointers to records
  505. CNodeClump()
  506. {
  507. Clear();
  508. }
  509. void
  510. Clear()
  511. {
  512. m_pncNext = NULL; // no dangling pointers
  513. for (DWORD i = 0; i < NODES_PER_CLUMP; ++i)
  514. {
  515. m_dwKeySigs[i] = HASH_INVALID_SIGNATURE;
  516. m_pvNode[i] = NULL;
  517. }
  518. }
  519. bool
  520. InvalidSignature(
  521. int i) const
  522. {
  523. IRTLASSERT(0 <= i && i < NODES_PER_CLUMP);
  524. return (m_dwKeySigs[i] == HASH_INVALID_SIGNATURE);
  525. }
  526. bool
  527. IsEmptyNode(
  528. int i) const
  529. {
  530. IRTLASSERT(0 <= i && i < NODES_PER_CLUMP);
  531. return (m_pvNode[i] == NULL);
  532. }
  533. bool
  534. IsLastClump() const
  535. {
  536. return (m_pncNext == NULL);
  537. }
  538. #ifdef _DEBUG
  539. // Don't want overhead of calls to dtor in retail build
  540. ~CNodeClump()
  541. {
  542. IRTLASSERT(IsLastClump()); // no dangling pointers
  543. for (DWORD i = 0; i < NODES_PER_CLUMP; ++i)
  544. IRTLASSERT(InvalidSignature(i) && IsEmptyNode(i));
  545. }
  546. #endif // _DEBUG
  547. LKRHASH_ALLOCATOR_DEFINITIONS(CNodeClump);
  548. };
  549. // Class for bucket chains of the hash table. Note that the first
  550. // nodeclump is actually included in the bucket and not dynamically
  551. // allocated, which increases space requirements slightly but does
  552. // improve performance.
  553. class CBucket
  554. {
  555. private:
  556. mutable BucketLock m_Lock; // lock protecting this bucket
  557. #ifdef LOCK_INSTRUMENTATION
  558. static LONG sm_cBuckets;
  559. static const char*
  560. _LockName()
  561. {
  562. LONG l = ++sm_cBuckets;
  563. // possible race condition but we don't care, as this is never
  564. // used in production code
  565. static char s_szName[CLockStatistics::L_NAMELEN];
  566. wsprintf(s_szName, "B%06x", 0xFFFFFF & l);
  567. return s_szName;
  568. }
  569. #endif // LOCK_INSTRUMENTATION
  570. public:
  571. CNodeClump m_ncFirst; // first CNodeClump of this bucket
  572. #if defined(LOCK_INSTRUMENTATION) || defined(_DEBUG)
  573. CBucket()
  574. #ifdef LOCK_INSTRUMENTATION
  575. : m_Lock(_LockName())
  576. #endif // LOCK_INSTRUMENTATION
  577. {
  578. #ifdef _DEBUG
  579. LOCK_LOCKTYPE lt = BucketLock::LockType();
  580. if (lt == LOCK_SPINLOCK || lt == LOCK_FAKELOCK)
  581. IRTLASSERT(sizeof(*this) <= 64);
  582. #endif _DEBUG
  583. }
  584. #endif // LOCK_INSTRUMENTATION || _DEBUG
  585. void WriteLock() { m_Lock.WriteLock(); }
  586. void ReadLock() const { m_Lock.ReadLock(); }
  587. void WriteUnlock() const { m_Lock.WriteUnlock(); }
  588. void ReadUnlock() const { m_Lock.ReadUnlock(); }
  589. bool IsWriteLocked() const { return m_Lock.IsWriteLocked(); }
  590. bool IsReadLocked() const { return m_Lock.IsReadLocked(); }
  591. bool IsWriteUnlocked() const { return m_Lock.IsWriteUnlocked(); }
  592. bool IsReadUnlocked() const { return m_Lock.IsReadUnlocked(); }
  593. void SetSpinCount(WORD wSpins) { m_Lock.SetSpinCount(wSpins); }
  594. WORD GetSpinCount() const { return m_Lock.GetSpinCount(); }
  595. #ifdef LOCK_INSTRUMENTATION
  596. CLockStatistics LockStats() const {return m_Lock.Statistics();}
  597. #endif // LOCK_INSTRUMENTATION
  598. };
  599. // The hash table space is divided into fixed-size segments (arrays of
  600. // CBuckets) and physically grows/shrinks one segment at a time.
  601. // We provide small, medium, and large segments to better tune the
  602. // overall memory requirements of the hash table according to the
  603. // expected usage of an instance.
  604. class CSegment
  605. {
  606. public:
  607. virtual ~CSegment() {}; // link fails if this is pure virtual
  608. virtual DWORD Bits() const = 0;
  609. virtual DWORD Size() const = 0;
  610. virtual DWORD Mask() const = 0;
  611. virtual DWORD InitSize() const = 0;
  612. virtual CBucket& Slot(DWORD i) = 0;
  613. };
  614. // Small-sized segments contain 2^3 = 8 buckets => ~0.5Kb
  615. class CSmallSegment : public CSegment
  616. {
  617. public:
  618. // Maximum table size equals MAX_DIRSIZE * SEGSIZE buckets.
  619. enum {
  620. SEGBITS = 3,// number of bits extracted from a hash
  621. // address for offset within a segment
  622. SEGSIZE = (1<<SEGBITS),// segment size
  623. SEGMASK = (SEGSIZE-1), // mask used for extracting offset bit
  624. INITSIZE = 1 * SEGSIZE, // #segments to allocate initially
  625. };
  626. private:
  627. CBucket m_bktSlots[SEGSIZE];
  628. public:
  629. virtual ~CSmallSegment() {}
  630. virtual DWORD Bits() const { return SEGBITS; }
  631. virtual DWORD Size() const { return SEGSIZE; }
  632. virtual DWORD Mask() const { return SEGMASK; }
  633. virtual DWORD InitSize() const { return INITSIZE;}
  634. virtual CBucket& Slot(DWORD i)
  635. { IRTLASSERT(i < SEGSIZE); return m_bktSlots[i]; }
  636. #ifdef _DEBUG
  637. CSmallSegment()
  638. {
  639. IRTLASSERT(((DWORD_PTR)this & (LKRHASH_MEM_DEFAULT_ALIGN-1)) == 0);
  640. IRTLASSERT(sizeof(*this)
  641. == SEGSIZE * sizeof(CBucket) + sizeof(void*));
  642. }
  643. #endif // _DEBUG
  644. LKRHASH_ALLOCATOR_DEFINITIONS(CSmallSegment);
  645. };
  646. // Medium-sized segments contain 2^6 = 64 buckets => ~4Kb
  647. class CMediumSegment : public CSegment
  648. {
  649. public:
  650. enum {
  651. SEGBITS = 6,
  652. SEGSIZE = (1<<SEGBITS),
  653. SEGMASK = (SEGSIZE-1),
  654. INITSIZE = 2 * SEGSIZE,
  655. };
  656. private:
  657. CBucket m_bktSlots[SEGSIZE];
  658. public:
  659. virtual ~CMediumSegment() {}
  660. virtual DWORD Bits() const { return SEGBITS; }
  661. virtual DWORD Size() const { return SEGSIZE; }
  662. virtual DWORD Mask() const { return SEGMASK; }
  663. virtual DWORD InitSize() const { return INITSIZE;}
  664. virtual CBucket& Slot(DWORD i)
  665. { IRTLASSERT(i < SEGSIZE); return m_bktSlots[i]; }
  666. #ifdef _DEBUG
  667. CMediumSegment()
  668. {
  669. IRTLASSERT(((DWORD_PTR)this & (LKRHASH_MEM_DEFAULT_ALIGN-1)) == 0);
  670. IRTLASSERT(sizeof(*this)
  671. == SEGSIZE * sizeof(CBucket) + sizeof(void*));
  672. }
  673. #endif // _DEBUG
  674. LKRHASH_ALLOCATOR_DEFINITIONS(CMediumSegment);
  675. };
  676. // Large-sized segments contain 2^9 = 512 buckets => ~32Kb
  677. class CLargeSegment : public CSegment
  678. {
  679. public:
  680. enum {
  681. SEGBITS = 9,
  682. SEGSIZE = (1<<SEGBITS),
  683. SEGMASK = (SEGSIZE-1),
  684. INITSIZE = 4 * SEGSIZE,
  685. };
  686. private:
  687. CBucket m_bktSlots[SEGSIZE];
  688. public:
  689. virtual ~CLargeSegment() {}
  690. virtual DWORD Bits() const { return SEGBITS; }
  691. virtual DWORD Size() const { return SEGSIZE; }
  692. virtual DWORD Mask() const { return SEGMASK; }
  693. virtual DWORD InitSize() const { return INITSIZE;}
  694. virtual CBucket& Slot(DWORD i)
  695. { IRTLASSERT(i < SEGSIZE); return m_bktSlots[i]; }
  696. #ifdef _DEBUG
  697. CLargeSegment()
  698. {
  699. IRTLASSERT(((DWORD_PTR)this & (LKRHASH_MEM_DEFAULT_ALIGN-1)) == 0);
  700. IRTLASSERT(sizeof(*this)
  701. == SEGSIZE * sizeof(CBucket) + sizeof(void*));
  702. }
  703. #endif // _DEBUG
  704. LKRHASH_ALLOCATOR_DEFINITIONS(CLargeSegment);
  705. };
  706. // A directory keeps track of the segments comprising the hash table.
  707. // The directory is just a variable-sized array of pointers to
  708. // segments (CDirEntrys).
  709. class CDirEntry
  710. {
  711. public:
  712. // MIN_DIRSIZE and MAX_DIRSIZE can be changed independently
  713. // of anything else. Should be powers of two.
  714. enum {
  715. MIN_DIRSIZE = (1<<3), // minimum directory size
  716. MAX_DIRSIZE = (1<<20), // maximum directory size
  717. };
  718. CSegment* m_pseg;
  719. CDirEntry()
  720. : m_pseg(NULL)
  721. {}
  722. ~CDirEntry()
  723. { delete m_pseg; }
  724. };
  725. public:
  726. // aliases for convenience
  727. enum {
  728. NODES_PER_CLUMP = CNodeClump::NODES_PER_CLUMP,
  729. MIN_DIRSIZE = CDirEntry::MIN_DIRSIZE,
  730. MAX_DIRSIZE = CDirEntry::MAX_DIRSIZE,
  731. NAME_SIZE = 16,
  732. };
  733. private:
  734. // Miscellaneous helper functions
  735. // Convert a hash signature to a bucket address
  736. inline DWORD _BucketAddress(DWORD dwSignature) const
  737. {
  738. DWORD dwBktAddr = _H0(dwSignature);
  739. // Has this bucket been split already?
  740. if (dwBktAddr < m_iExpansionIdx)
  741. dwBktAddr = _H1(dwSignature);
  742. IRTLASSERT(dwBktAddr < m_cActiveBuckets);
  743. IRTLASSERT(dwBktAddr < (m_cDirSegs << m_dwSegBits));
  744. return dwBktAddr;
  745. }
  746. // See the Linear Hashing paper
  747. static DWORD _H0(DWORD dwSignature, DWORD dwBktAddrMask)
  748. { return dwSignature & dwBktAddrMask; }
  749. DWORD _H0(DWORD dwSignature) const
  750. { return _H0(dwSignature, m_dwBktAddrMask); }
  751. // See the Linear Hashing paper. Preserves one bit more than _H0.
  752. static DWORD _H1(DWORD dwSignature, DWORD dwBktAddrMask)
  753. { return dwSignature & ((dwBktAddrMask << 1) | 1); }
  754. DWORD _H1(DWORD dwSignature) const
  755. #ifdef LKR_MASK
  756. { return _H0(dwSignature, m_dwBktAddrMask1); }
  757. #else // !LKR_MASK
  758. { return _H1(dwSignature, m_dwBktAddrMask); }
  759. #endif // !LKR_MASK
  760. // In which segment within the directory does the bucketaddress lie?
  761. // (Return type must be lvalue so that it can be assigned to.)
  762. CSegment*& _Segment(DWORD dwBucketAddr) const
  763. {
  764. const DWORD iSeg = dwBucketAddr >> m_dwSegBits;
  765. IRTLASSERT(m_paDirSegs != NULL && iSeg < m_cDirSegs);
  766. return m_paDirSegs[iSeg].m_pseg;
  767. }
  768. // Offset within the segment of the bucketaddress
  769. DWORD _SegIndex(DWORD dwBucketAddr) const
  770. { return (dwBucketAddr & m_dwSegMask); }
  771. // Convert a bucketaddress to a CBucket*
  772. inline CBucket* _Bucket(DWORD dwBucketAddr) const
  773. {
  774. IRTLASSERT(dwBucketAddr < m_cActiveBuckets);
  775. CSegment* const pseg = _Segment(dwBucketAddr);
  776. IRTLASSERT(pseg != NULL);
  777. return &(pseg->Slot(_SegIndex(dwBucketAddr)));
  778. }
  779. // Extract the key from a record
  780. const DWORD_PTR _ExtractKey(const void* pvRecord) const
  781. {
  782. IRTLASSERT(pvRecord != NULL);
  783. IRTLASSERT(m_pfnExtractKey != NULL);
  784. #ifdef LKR_SUBTABLE
  785. return (*m_pfnExtractKey)(pvRecord);
  786. #else
  787. return (pvRecord != NULL) ? (*m_pfnExtractKey)(pvRecord) : NULL;
  788. #endif
  789. }
  790. // Hash a key
  791. DWORD _CalcKeyHash(const DWORD_PTR pnKey) const
  792. {
  793. // Note pnKey==0 is acceptable, as the real key type could be an int
  794. IRTLASSERT(m_pfnCalcKeyHash != NULL);
  795. DWORD dwHash = (*m_pfnCalcKeyHash)(pnKey);
  796. // We forcibly scramble the result to help ensure a better distribution
  797. dwHash = HashScramble(dwHash);
  798. IRTLASSERT(dwHash != HASH_INVALID_SIGNATURE);
  799. return dwHash;
  800. }
  801. // Compare two keys for equality
  802. bool _EqualKeys(const DWORD_PTR pnKey1, const DWORD_PTR pnKey2) const
  803. {
  804. IRTLASSERT(m_pfnEqualKeys != NULL);
  805. return (*m_pfnEqualKeys)(pnKey1, pnKey2);
  806. }
  807. // AddRef or Release a record.
  808. void _AddRefRecord(const void* pvRecord, int nIncr) const
  809. {
  810. IRTLASSERT(pvRecord != NULL && (nIncr == -1 || nIncr == +1));
  811. (*m_pfnAddRefRecord)(pvRecord, nIncr);
  812. }
  813. // Find a bucket, given its signature. The bucket is locked before
  814. // returning. Assumes table is already locked, to avoid race conditions.
  815. CBucket* _FindBucket(DWORD dwSignature, bool fLockForWrite) const
  816. {
  817. IRTLASSERT(IsValid());
  818. IRTLASSERT(m_dwBktAddrMask > 0);
  819. IRTLASSERT((m_dwBktAddrMask & (m_dwBktAddrMask+1)) == 0); // 00011..111
  820. IRTLASSERT(m_dwBktAddrMask == (1U << m_nLevel) - 1);
  821. #ifdef LKR_MASK
  822. IRTLASSERT(m_dwBktAddrMask1 == ((m_dwBktAddrMask << 1) | 1));
  823. IRTLASSERT((m_dwBktAddrMask1 & (m_dwBktAddrMask1+1)) == 0);
  824. #endif // LKR_MASK
  825. IRTLASSERT(m_iExpansionIdx <= m_dwBktAddrMask);
  826. IRTLASSERT(0 < m_dwSegBits && m_dwSegBits < 20
  827. && m_dwSegSize == (1U << m_dwSegBits)
  828. && m_dwSegMask == (m_dwSegSize - 1));
  829. IRTLASSERT(IsReadLocked() || IsWriteLocked());
  830. const DWORD dwBktAddr = _BucketAddress(dwSignature);
  831. IRTLASSERT(dwBktAddr < m_cActiveBuckets);
  832. CBucket* const pbkt = _Bucket(dwBktAddr);
  833. IRTLASSERT(pbkt != NULL);
  834. if (fLockForWrite)
  835. pbkt->WriteLock();
  836. else
  837. pbkt->ReadLock();
  838. return pbkt;
  839. }
  840. // Memory allocation wrappers to allow us to simulate allocation
  841. // failures during testing
  842. static CDirEntry* const
  843. _AllocateSegmentDirectory(
  844. size_t n);
  845. static bool
  846. _FreeSegmentDirectory(
  847. CDirEntry* paDirSegs);
  848. static CNodeClump* const
  849. _AllocateNodeClump();
  850. static bool
  851. _FreeNodeClump(
  852. CNodeClump* pnc);
  853. CSegment* const
  854. _AllocateSegment() const;
  855. bool
  856. _FreeSegment(
  857. CSegment* pseg) const;
  858. #ifdef LOCK_INSTRUMENTATION
  859. static LONG sm_cTables;
  860. static const char*
  861. _LockName()
  862. {
  863. LONG l = ++sm_cTables;
  864. // possible race condition but we don't care, as this is never
  865. // used in production code
  866. static char s_szName[CLockStatistics::L_NAMELEN];
  867. wsprintf(s_szName, "LH%05x", 0xFFFFF & l);
  868. return s_szName;
  869. }
  870. // Statistics for the table lock
  871. CLockStatistics _LockStats() const
  872. { return m_Lock.Statistics(); }
  873. #endif // LOCK_INSTRUMENTATION
  874. private:
  875. // Fields are ordered so as to minimize number of cache lines touched
  876. DWORD m_dwSignature; // debugging: id & corruption check
  877. CHAR m_szName[NAME_SIZE]; // an identifier for debugging
  878. mutable LK_RETCODE m_lkrcState; // Internal state of table
  879. mutable TableLock m_Lock; // Lock on entire linear hash table
  880. // type-specific function pointers
  881. PFnExtractKey m_pfnExtractKey; // Extract key from record
  882. PFnCalcKeyHash m_pfnCalcKeyHash; // Calculate hash signature of key
  883. PFnEqualKeys m_pfnEqualKeys; // Compare two keys
  884. PFnAddRefRecord m_pfnAddRefRecord; // AddRef a record
  885. LK_TABLESIZE m_lkts; // "size" of table: small, medium, or large
  886. DWORD m_dwSegBits; // C{Small,Medium,Large}Segment::SEGBITS
  887. DWORD m_dwSegSize; // C{Small,Medium,Large}Segment::SEGSIZE
  888. DWORD m_dwSegMask; // C{Small,Medium,Large}Segment::SEGMASK
  889. double m_MaxLoad; // max load factor (average chain length)
  890. DWORD m_dwBktAddrMask; // mask used for address calculation
  891. #ifdef LKR_MASK
  892. DWORD m_dwBktAddrMask1; // used in _H1 calculation
  893. #endif // LKR_MASK
  894. DWORD m_iExpansionIdx; // address of next bucket to be expanded
  895. CDirEntry* m_paDirSegs; // directory of table segments
  896. DWORD m_nLevel; // number of table doublings performed
  897. DWORD m_cDirSegs; // segment directory size: varies between
  898. // MIN_DIRSIZE and MAX_DIRSIZE
  899. DWORD m_cRecords; // number of records in the table
  900. DWORD m_cActiveBuckets; // number of buckets in use (table size)
  901. WORD m_wBucketLockSpins;// default spin count for bucket locks
  902. #ifdef LKR_NEWCODE
  903. const BYTE m_nTableLockType; // for debugging: LOCK_SPINLOCK, etc
  904. const BYTE m_nBucketLockType;// for debugging: LOCK_SPINLOCK, etc
  905. const CLKRHashTable* const m_phtParent;// Owning table. NULL => standalone
  906. static CLockedDoubleList sm_llGlobalList;// All active CLKRLinearHashTables
  907. CListEntry m_leGlobalList;
  908. void _InsertThisIntoGlobalList()
  909. {
  910. // Only add standalone CLKRLinearHashTables to global list.
  911. // CLKRHashTables have their own global list.
  912. if (m_phtParent == NULL)
  913. sm_llGlobalList.InsertHead(&m_leGlobalList);
  914. }
  915. void _RemoveThisFromGlobalList()
  916. {
  917. if (m_phtParent == NULL)
  918. sm_llGlobalList.RemoveEntry(&m_leGlobalList);
  919. }
  920. #endif // LKR_NEWCODE
  921. // Non-trivial implementation functions
  922. LK_RETCODE _InsertRecord(const void* pvRecord, DWORD dwSignature,
  923. bool fOverwrite);
  924. LK_RETCODE _DeleteKey(const DWORD_PTR pnKey, DWORD dwSignature);
  925. LK_RETCODE _DeleteRecord(const void* pvRecord, DWORD dwSignature);
  926. bool _DeleteNode(CBucket* pbkt, CNodeClump*& rpnc,
  927. CNodeClump*& rpncPrev, int& riNode);
  928. LK_RETCODE _FindKey(const DWORD_PTR pnKey, DWORD dwSignature,
  929. const void** ppvRecord) const;
  930. LK_RETCODE _FindRecord(const void* pvRecord, DWORD dwSignature) const;
  931. #ifdef LKR_COMPACT_DELETE
  932. // returns count of errors in compacted state => 0 is good
  933. int _IsNodeCompact(CBucket* const pbkt) const;
  934. #endif // LKR_COMPACT_DELETE
  935. // Predicate functions
  936. static LK_PREDICATE _PredTrue(const void* /*pvRecord*/, void* /*pvState*/)
  937. { return LKP_PERFORM; }
  938. DWORD _Apply(PFnRecordAction pfnAction, void* pvState,
  939. LK_LOCKTYPE lkl, LK_PREDICATE& rlkp);
  940. DWORD _ApplyIf(PFnRecordPred pfnPredicate,
  941. PFnRecordAction pfnAction, void* pvState,
  942. LK_LOCKTYPE lkl, LK_PREDICATE& rlkp);
  943. DWORD _DeleteIf(PFnRecordPred pfnPredicate, void* pvState,
  944. LK_PREDICATE& rlkp);
  945. void _Clear(bool fShrinkDirectory);
  946. void _SetSegVars(LK_TABLESIZE lkts);
  947. LK_RETCODE _Expand();
  948. LK_RETCODE _Contract();
  949. LK_RETCODE _SplitRecordSet(CNodeClump* pncOldTarget,
  950. CNodeClump* pncNewTarget,
  951. DWORD iExpansionIdx,
  952. DWORD dwBktAddrMask,
  953. DWORD dwNewBkt,
  954. CNodeClump* pncFreeList);
  955. LK_RETCODE _MergeRecordSets(CBucket* pbktNewTarget,
  956. CNodeClump* pncOldList,
  957. CNodeClump* pncFreeList);
  958. // Private copy ctor and op= to prevent compiler synthesizing them.
  959. // Must provide a (bad) implementation because we export instantiations.
  960. // TODO: implement these properly; they could be useful.
  961. CLKRLinearHashTable(const CLKRLinearHashTable&)
  962. : m_dwSignature(SIGNATURE_FREE)
  963. #ifdef LOCK_INSTRUMENTATION
  964. , m_Lock(NULL)
  965. #endif // LOCK_INSTRUMENTATION
  966. #ifdef LKR_NEWCODE
  967. , m_nTableLockType(0),
  968. m_nBucketLockType(0),
  969. m_phtParent(NULL)
  970. #endif // LKR_NEWCODE
  971. {*(BYTE*)NULL;}
  972. CLKRLinearHashTable& operator=(const CLKRLinearHashTable&)
  973. {return *(CLKRLinearHashTable*)NULL;}
  974. #ifdef LKR_NEWCODE
  975. private:
  976. // This ctor is used by CLKRHashTable
  977. CLKRLinearHashTable(
  978. LPCSTR pszName, // An identifier for debugging
  979. PFnExtractKey pfnExtractKey, // Extract key from record
  980. PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
  981. PFnEqualKeys pfnEqualKeys, // Compare two keys
  982. PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
  983. double maxload, // Upperbound on average chain length
  984. DWORD initsize, // Initial size of hash table.
  985. CLKRHashTable* phtParent // Owning table.
  986. );
  987. #endif // LKR_NEWCODE
  988. LK_RETCODE
  989. _Initialize(
  990. PFnExtractKey pfnExtractKey,
  991. PFnCalcKeyHash pfnCalcKeyHash,
  992. PFnEqualKeys pfnEqualKeys,
  993. PFnAddRefRecord pfnAddRefRecord,
  994. LPCSTR pszName,
  995. double maxload,
  996. DWORD initsize);
  997. public:
  998. CLKRLinearHashTable(
  999. LPCSTR pszName, // An identifier for debugging
  1000. PFnExtractKey pfnExtractKey, // Extract key from record
  1001. PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
  1002. PFnEqualKeys pfnEqualKeys, // Compare two keys
  1003. PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
  1004. double maxload=LK_DFLT_MAXLOAD,// Upperbound on average chain length
  1005. DWORD initsize=LK_DFLT_INITSIZE, // Initial size of hash table.
  1006. DWORD num_subtbls=LK_DFLT_NUM_SUBTBLS // for signature compatiblity
  1007. // with CLKRHashTable
  1008. );
  1009. ~CLKRLinearHashTable();
  1010. static const char* ClassName() {return "CLKRLinearHashTable";}
  1011. int NumSubTables() const {return 1;}
  1012. static LK_TABLESIZE NumSubTables(DWORD& rinitsize, DWORD& rnum_subtbls);
  1013. // Insert a new record into hash table.
  1014. // Returns LK_SUCCESS if all OK, LK_KEY_EXISTS if same key already
  1015. // exists (unless fOverwrite), LK_ALLOC_FAIL if out of space,
  1016. // or LK_BAD_RECORD for a bad record.
  1017. LK_RETCODE InsertRecord(const void* pvRecord, bool fOverwrite=false)
  1018. {
  1019. #ifdef LKR_SUBTABLE
  1020. if (!IsUsable())
  1021. return m_lkrcState;
  1022. if (pvRecord == NULL)
  1023. return LK_BAD_RECORD;
  1024. #endif
  1025. return _InsertRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)),
  1026. fOverwrite);
  1027. }
  1028. // Delete record with the given key.
  1029. // Returns LK_SUCCESS if all OK, or LK_NO_SUCH_KEY if not found
  1030. LK_RETCODE DeleteKey(const DWORD_PTR pnKey)
  1031. {
  1032. #ifdef LKR_SUBTABLE
  1033. if (!IsUsable())
  1034. return m_lkrcState;
  1035. #endif
  1036. return _DeleteKey(pnKey, _CalcKeyHash(pnKey));
  1037. }
  1038. // Delete a record from the table, if present.
  1039. // Returns LK_SUCCESS if all OK, or LK_NO_SUCH_KEY if not found
  1040. LK_RETCODE DeleteRecord(const void* pvRecord)
  1041. {
  1042. #ifdef LKR_SUBTABLE
  1043. if (!IsUsable())
  1044. return m_lkrcState;
  1045. if (pvRecord == NULL)
  1046. return LK_BAD_RECORD;
  1047. #endif
  1048. return _DeleteRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)));
  1049. }
  1050. // Find record with given key.
  1051. // Returns: LK_SUCCESS, if record found (record is returned in *ppvRecord)
  1052. // LK_BAD_RECORD, if ppvRecord is invalid
  1053. // LK_NO_SUCH_KEY, if no record with given key value was found
  1054. // LK_UNUSABLE, if hash table not in usable state
  1055. // Note: the record is AddRef'd. You must decrement the reference
  1056. // count when you are finished with the record (if you're implementing
  1057. // refcounting semantics).
  1058. LK_RETCODE FindKey(const DWORD_PTR pnKey,
  1059. const void** ppvRecord) const
  1060. {
  1061. #ifdef LKR_SUBTABLE
  1062. if (!IsUsable())
  1063. return m_lkrcState;
  1064. if (ppvRecord == NULL)
  1065. return LK_BAD_RECORD;
  1066. #endif
  1067. return _FindKey(pnKey, _CalcKeyHash(pnKey), ppvRecord);
  1068. }
  1069. // Sees if the record is contained in the table
  1070. // Returns: LK_SUCCESS, if record found
  1071. // LK_BAD_RECORD, if pvRecord is invalid
  1072. // LK_NO_SUCH_KEY, if record is not in the table
  1073. // LK_UNUSABLE, if hash table not in usable state
  1074. // Note: the record is *not* AddRef'd.
  1075. LK_RETCODE FindRecord(const void* pvRecord) const
  1076. {
  1077. #ifdef LKR_SUBTABLE
  1078. if (!IsUsable())
  1079. return m_lkrcState;
  1080. if (pvRecord == NULL)
  1081. return LK_BAD_RECORD;
  1082. #endif
  1083. return _FindRecord(pvRecord, _CalcKeyHash(_ExtractKey(pvRecord)));
  1084. }
  1085. // Walk the hash table, applying pfnAction to all records.
  1086. // Locks the whole table for the duration with either a (possibly
  1087. // shared) readlock or a writelock, according to lkl.
  1088. // Loop is aborted if pfnAction returns LKA_ABORT.
  1089. // Returns the number of successful applications.
  1090. DWORD Apply(PFnRecordAction pfnAction,
  1091. void* pvState=NULL,
  1092. LK_LOCKTYPE lkl=LKL_READLOCK);
  1093. // Walk the hash table, applying pfnAction to any records that match
  1094. // pfnPredicate. Locks the whole table for the duration with either
  1095. // a (possibly shared) readlock or a writelock, according to lkl.
  1096. // Loop is aborted if pfnAction returns LKA_ABORT.
  1097. // Returns the number of successful applications.
  1098. DWORD ApplyIf(PFnRecordPred pfnPredicate,
  1099. PFnRecordAction pfnAction,
  1100. void* pvState=NULL,
  1101. LK_LOCKTYPE lkl=LKL_READLOCK);
  1102. // Delete any records that match pfnPredicate.
  1103. // Locks the table for the duration with a writelock.
  1104. // Returns the number of deletions.
  1105. //
  1106. // Do *not* walk the hash table by hand with an iterator and call
  1107. // DeleteKey. The iterator will end up pointing to garbage.
  1108. DWORD DeleteIf(PFnRecordPred pfnPredicate,
  1109. void* pvState=NULL);
  1110. // Check table for consistency. Returns 0 if okay, or the number of
  1111. // errors otherwise.
  1112. int CheckTable() const;
  1113. // Prints the table (to where??)
  1114. void Print() const;
  1115. // Remove all data from the table
  1116. void Clear()
  1117. {
  1118. WriteLock();
  1119. _Clear(true);
  1120. WriteUnlock();
  1121. }
  1122. // Number of elements in the table
  1123. DWORD Size() const
  1124. { return m_cRecords; }
  1125. // Maximum possible number of elements in the table
  1126. DWORD MaxSize() const
  1127. { return static_cast<DWORD>(m_MaxLoad * MAX_DIRSIZE * m_dwSegSize); }
  1128. // Get hash table statistics
  1129. CLKRHashTableStats GetStatistics() const;
  1130. // Is the hash table usable?
  1131. bool IsUsable() const
  1132. { return (m_lkrcState == LK_SUCCESS); }
  1133. // Is the hash table consistent and correct?
  1134. bool IsValid() const
  1135. {
  1136. bool f = (m_lkrcState == LK_SUCCESS // serious internal failure?
  1137. && m_paDirSegs != NULL
  1138. && (MIN_DIRSIZE & (MIN_DIRSIZE-1)) == 0 // == (1 << N)
  1139. && (MAX_DIRSIZE & (MAX_DIRSIZE-1)) == 0
  1140. && MAX_DIRSIZE > MIN_DIRSIZE
  1141. && MIN_DIRSIZE <= m_cDirSegs && m_cDirSegs <= MAX_DIRSIZE
  1142. && (m_cDirSegs & (m_cDirSegs-1)) == 0
  1143. && m_pfnExtractKey != NULL
  1144. && m_pfnCalcKeyHash != NULL
  1145. && m_pfnEqualKeys != NULL
  1146. && m_cActiveBuckets > 0
  1147. && ValidSignature()
  1148. );
  1149. if (!f)
  1150. m_lkrcState = LK_UNUSABLE;
  1151. return f;
  1152. }
  1153. // Set the spin count on the table lock
  1154. void SetTableLockSpinCount(WORD wSpins)
  1155. { m_Lock.SetSpinCount(wSpins); }
  1156. // Get the spin count on the table lock
  1157. WORD GetTableLockSpinCount()
  1158. { return m_Lock.GetSpinCount(); }
  1159. // Set/Get the spin count on the bucket locks
  1160. void SetBucketLockSpinCount(WORD wSpins);
  1161. WORD GetBucketLockSpinCount();
  1162. enum {
  1163. SIGNATURE = (('L') | ('K' << 8) | ('L' << 16) | ('H' << 24)),
  1164. SIGNATURE_FREE = (('L') | ('K' << 8) | ('L' << 16) | ('x' << 24)),
  1165. };
  1166. bool
  1167. ValidSignature() const
  1168. { return m_dwSignature == SIGNATURE;}
  1169. // Lock manipulators
  1170. // Lock the table (exclusively) for writing
  1171. void WriteLock()
  1172. { m_Lock.WriteLock(); }
  1173. // Lock the table (possibly shared) for reading
  1174. void ReadLock() const
  1175. { m_Lock.ReadLock(); }
  1176. // Unlock the table for writing
  1177. void WriteUnlock() const
  1178. { m_Lock.WriteUnlock(); }
  1179. // Unlock the table for reading
  1180. void ReadUnlock() const
  1181. { m_Lock.ReadUnlock(); }
  1182. // Is the table already locked for writing?
  1183. bool IsWriteLocked() const
  1184. { return m_Lock.IsWriteLocked(); }
  1185. // Is the table already locked for reading?
  1186. bool IsReadLocked() const
  1187. { return m_Lock.IsReadLocked(); }
  1188. // Is the table unlocked for writing?
  1189. bool IsWriteUnlocked() const
  1190. { return m_Lock.IsWriteUnlocked(); }
  1191. // Is the table unlocked for reading?
  1192. bool IsReadUnlocked() const
  1193. { return m_Lock.IsReadUnlocked(); }
  1194. // LKRHASH_ALLOCATOR_DEFINITIONS(CLKRLinearHashTable);
  1195. public:
  1196. // Iterators can be used to walk the table. To ensure a consistent
  1197. // view of the data, the iterator locks the whole table. This can
  1198. // have a negative effect upon performance, because no other thread
  1199. // can do anything with the table. Use with care.
  1200. //
  1201. // You should not use an iterator to walk the table, calling DeleteKey,
  1202. // as the iterator will end up pointing to garbage.
  1203. //
  1204. // Use Apply, ApplyIf, or DeleteIf instead of iterators to safely
  1205. // walk the tree.
  1206. //
  1207. // Note that iterators acquire a reference to the record pointed to
  1208. // and release that reference as soon as the iterator is incremented.
  1209. // In other words, this code is safe:
  1210. // lkrc = ht.IncrementIterator(&iter);
  1211. // // assume lkrc == LK_SUCCESS for the sake of this example
  1212. // CMyHashTable::Record* pRec = iter.Record();
  1213. // Foo(pRec); // uses pRec but doesn't hang on to it
  1214. // lkrc = ht.IncrementIterator(&iter);
  1215. //
  1216. // But this code is not safe because pRec is used out of the scope of the
  1217. // iterator that provided it:
  1218. // lkrc = ht.IncrementIterator(&iter);
  1219. // CMyHashTable::Record* pRec = iter.Record();
  1220. // // Should call ht.AddRefRecord(pRec, +1) here
  1221. // lkrc = ht.IncrementIterator(&iter);
  1222. // Foo(pRec);
  1223. //
  1224. // If record has no reference-counting semantics, then you can ignore
  1225. // the above remarks about scope.
  1226. class CIterator
  1227. {
  1228. protected:
  1229. friend class CLKRLinearHashTable;
  1230. CLKRLinearHashTable* m_plht; // which linear hash table?
  1231. DWORD m_dwBucketAddr; // bucket index
  1232. CNodeClump* m_pnc; // a CNodeClump in bucket
  1233. int m_iNode; // offset within m_pnc
  1234. LK_LOCKTYPE m_lkl; // readlock or writelock?
  1235. private:
  1236. // Private copy ctor and op= to prevent compiler synthesizing them.
  1237. // Must provide (bad) implementation because we export instantiations.
  1238. CIterator(const CIterator&) {*(BYTE*)NULL;}
  1239. CIterator& operator=(const CIterator&) {return *(CIterator*)NULL;}
  1240. public:
  1241. CIterator(
  1242. LK_LOCKTYPE lkl=LKL_WRITELOCK)
  1243. : m_plht(NULL),
  1244. m_dwBucketAddr(0),
  1245. m_pnc(NULL),
  1246. m_iNode(-1),
  1247. m_lkl(lkl)
  1248. {}
  1249. // Return the record associated with this iterator
  1250. const void* Record() const
  1251. {
  1252. IRTLASSERT(IsValid());
  1253. return ((m_pnc != NULL
  1254. && m_iNode >= 0
  1255. && m_iNode < CLKRLinearHashTable::NODES_PER_CLUMP)
  1256. ? m_pnc->m_pvNode[m_iNode]
  1257. : NULL);
  1258. }
  1259. // Return the key associated with this iterator
  1260. const DWORD_PTR Key() const
  1261. {
  1262. IRTLASSERT(m_plht != NULL);
  1263. const void* pRec = Record();
  1264. return ((pRec != NULL && m_plht != NULL)
  1265. ? m_plht->_ExtractKey(pRec)
  1266. : NULL);
  1267. }
  1268. bool IsValid() const
  1269. {
  1270. return ((m_plht != NULL)
  1271. && (m_pnc != NULL)
  1272. && (0 <= m_iNode
  1273. && m_iNode < CLKRLinearHashTable::NODES_PER_CLUMP)
  1274. && (!m_pnc->IsEmptyNode(m_iNode)));
  1275. }
  1276. // Delete the record that the iterator points to. Does an implicit
  1277. // IncrementIterator after deletion.
  1278. LK_RETCODE DeleteRecord();
  1279. // Change the record that the iterator points to. The new record
  1280. // must have the same key as the old one.
  1281. LK_RETCODE ChangeRecord(const void* pNewRec);
  1282. };
  1283. // Const iterators for readonly access. You must use these with
  1284. // const CLKRLinearHashTables.
  1285. class CConstIterator : public CIterator
  1286. {
  1287. private:
  1288. // Private, unimplemented copy ctor and op= to prevent
  1289. // compiler synthesizing them.
  1290. CConstIterator(const CConstIterator&);
  1291. CConstIterator& operator=(const CConstIterator&);
  1292. public:
  1293. CConstIterator()
  1294. : CIterator(LKL_READLOCK)
  1295. {}
  1296. };
  1297. private:
  1298. // The public APIs lock the table. The private ones, which are used
  1299. // directly by CLKRHashTable, don't.
  1300. LK_RETCODE _InitializeIterator(CIterator* piter);
  1301. LK_RETCODE _CloseIterator(CIterator* piter);
  1302. public:
  1303. // Initialize the iterator to point to the first item in the hash table
  1304. // Returns LK_SUCCESS, LK_NO_MORE_ELEMENTS, or LK_BAD_ITERATOR.
  1305. LK_RETCODE InitializeIterator(CIterator* piter)
  1306. {
  1307. IRTLASSERT(piter != NULL && piter->m_plht == NULL);
  1308. if (piter == NULL || piter->m_plht != NULL)
  1309. return LK_BAD_ITERATOR;
  1310. if (piter->m_lkl == LKL_WRITELOCK)
  1311. WriteLock();
  1312. else
  1313. ReadLock();
  1314. return _InitializeIterator(piter);
  1315. }
  1316. // The const iterator version
  1317. LK_RETCODE InitializeIterator(CConstIterator* piter) const
  1318. {
  1319. IRTLASSERT(piter != NULL && piter->m_plht == NULL);
  1320. IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
  1321. if (piter == NULL || piter->m_plht != NULL
  1322. || piter->m_lkl == LKL_WRITELOCK)
  1323. return LK_BAD_ITERATOR;
  1324. ReadLock();
  1325. return const_cast<CLKRLinearHashTable*>(this)
  1326. ->_InitializeIterator(static_cast<CIterator*>(piter));
  1327. }
  1328. // Move the iterator on to the next item in the table.
  1329. // Returns LK_SUCCESS, LK_NO_MORE_ELEMENTS, or LK_BAD_ITERATOR.
  1330. LK_RETCODE IncrementIterator(CIterator* piter);
  1331. LK_RETCODE IncrementIterator(CConstIterator* piter) const
  1332. {
  1333. IRTLASSERT(piter != NULL && piter->m_plht == this);
  1334. IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
  1335. if (piter == NULL || piter->m_plht != this
  1336. || piter->m_lkl == LKL_WRITELOCK)
  1337. return LK_BAD_ITERATOR;
  1338. return const_cast<CLKRLinearHashTable*>(this)
  1339. ->IncrementIterator(static_cast<CIterator*>(piter));
  1340. }
  1341. // Close the iterator.
  1342. LK_RETCODE CloseIterator(CIterator* piter)
  1343. {
  1344. IRTLASSERT(piter != NULL && piter->m_plht == this);
  1345. if (piter == NULL || piter->m_plht != this)
  1346. return LK_BAD_ITERATOR;
  1347. _CloseIterator(piter);
  1348. if (piter->m_lkl == LKL_WRITELOCK)
  1349. WriteUnlock();
  1350. else
  1351. ReadUnlock();
  1352. return LK_SUCCESS;
  1353. };
  1354. // Close the CConstIterator
  1355. LK_RETCODE CloseIterator(CConstIterator* piter) const
  1356. {
  1357. IRTLASSERT(piter != NULL && piter->m_plht == this);
  1358. IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
  1359. if (piter == NULL || piter->m_plht != this
  1360. || piter->m_lkl == LKL_WRITELOCK)
  1361. return LK_BAD_ITERATOR;
  1362. const_cast<CLKRLinearHashTable*>(this)
  1363. ->_CloseIterator(static_cast<CIterator*>(piter));
  1364. ReadUnlock();
  1365. return LK_SUCCESS;
  1366. };
  1367. };
  1368. //--------------------------------------------------------------------
  1369. // CLKRHashTable
  1370. //
  1371. // To improve concurrency, a hash table is divided into a number of
  1372. // (independent) subtables. Each subtable is a linear hash table. The
  1373. // number of subtables is defined when the table is created and remains
  1374. // fixed thereafter. Records are assigned to subtables based on their
  1375. // hashed key.
  1376. //
  1377. // For small or low-contention hashtables, you can bypass this
  1378. // thin wrapper and use CLKRLinearHashTable directly. The methods are
  1379. // documented in the declarations for CLKRHashTable (above).
  1380. //--------------------------------------------------------------------
  1381. class IRTL_DLLEXP CLKRHashTable
  1382. {
  1383. private:
  1384. typedef CLKRLinearHashTable SubTable;
  1385. public:
  1386. typedef SubTable::TableLock TableLock;
  1387. typedef SubTable::BucketLock BucketLock;
  1388. class CIterator;
  1389. friend class CLKRHashTable::CIterator;
  1390. friend class CLKRLinearHashTable;
  1391. // aliases for convenience
  1392. enum {
  1393. NAME_SIZE = SubTable::NAME_SIZE,
  1394. HASH_INVALID_SIGNATURE = SubTable::HASH_INVALID_SIGNATURE,
  1395. NODES_PER_CLUMP = SubTable::NODES_PER_CLUMP,
  1396. };
  1397. private:
  1398. // Hash table parameters
  1399. DWORD m_dwSignature; // debugging: id & corruption check
  1400. CHAR m_szName[NAME_SIZE]; // an identifier for debugging
  1401. DWORD m_cSubTables; // number of subtables
  1402. SubTable** m_palhtDir; // array of subtables
  1403. // type-specific function pointers
  1404. PFnExtractKey m_pfnExtractKey;
  1405. PFnCalcKeyHash m_pfnCalcKeyHash;
  1406. mutable LK_RETCODE m_lkrcState; // Internal state of table
  1407. #ifdef LKR_NEWCODE
  1408. static CLockedDoubleList sm_llGlobalList; // All active CLKRHashTables
  1409. CListEntry m_leGlobalList;
  1410. void
  1411. _InsertThisIntoGlobalList()
  1412. {
  1413. sm_llGlobalList.InsertHead(&m_leGlobalList);
  1414. }
  1415. void
  1416. _RemoveThisFromGlobalList()
  1417. {
  1418. sm_llGlobalList.RemoveEntry(&m_leGlobalList);
  1419. }
  1420. #endif // LKR_NEWCODE
  1421. LKRHASH_GLOBAL_LOCK_DECLARATIONS();
  1422. // Private copy ctor and op= to prevent compiler synthesizing them.
  1423. // Must provide a (bad) implementation because we export instantiations.
  1424. // TODO: implement these properly; they could be useful.
  1425. CLKRHashTable(const CLKRHashTable&) {*(BYTE*)NULL;}
  1426. CLKRHashTable& operator=(const CLKRHashTable&) {return *(CLKRHashTable*)NULL;}
  1427. // Extract the key from the record
  1428. const DWORD_PTR _ExtractKey(const void* pvRecord) const
  1429. {
  1430. IRTLASSERT(pvRecord != NULL);
  1431. IRTLASSERT(m_pfnExtractKey != NULL);
  1432. #ifdef LKR_SUBTABLE
  1433. return (*m_pfnExtractKey)(pvRecord);
  1434. #else
  1435. return (pvRecord != NULL) ? (*m_pfnExtractKey)(pvRecord) : NULL;
  1436. #endif
  1437. }
  1438. // Hash the key
  1439. DWORD _CalcKeyHash(const DWORD_PTR pnKey) const
  1440. {
  1441. // Note pnKey==0 is acceptable, as the real key type could be an int
  1442. IRTLASSERT(m_pfnCalcKeyHash != NULL);
  1443. DWORD dwHash = (*m_pfnCalcKeyHash)(pnKey);
  1444. // We forcibly scramble the result to help ensure a better distribution
  1445. dwHash = HashScramble(dwHash);
  1446. IRTLASSERT(dwHash != HASH_INVALID_SIGNATURE);
  1447. return dwHash;
  1448. }
  1449. // Use the key's hash signature to multiplex into a subtable
  1450. SubTable* _SubTable(DWORD dwSignature) const
  1451. {
  1452. IRTLASSERT(m_lkrcState == LK_SUCCESS
  1453. && m_palhtDir != NULL && m_cSubTables > 0);
  1454. #ifndef LKR_SUBTABLE
  1455. if (m_lkrcState == LK_SUCCESS)
  1456. {
  1457. #endif
  1458. const DWORD PRIME = 1048583UL; // used to scramble the hash sig
  1459. DWORD index = (dwSignature % PRIME) % m_cSubTables;
  1460. return m_palhtDir[index];
  1461. #ifndef LKR_SUBTABLE
  1462. }
  1463. else
  1464. return NULL;
  1465. #endif
  1466. }
  1467. // Memory allocation wrappers to allow us to simulate allocation
  1468. // failures during testing
  1469. static SubTable** const
  1470. _AllocateSubTableArray(
  1471. size_t n);
  1472. static bool
  1473. _FreeSubTableArray(
  1474. SubTable** palht);
  1475. static SubTable* const
  1476. _AllocateSubTable(
  1477. LPCSTR pszName, // An identifier for debugging
  1478. PFnExtractKey pfnExtractKey, // Extract key from record
  1479. PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
  1480. PFnEqualKeys pfnEqualKeys, // Compare two keys
  1481. PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
  1482. double maxload, // Upperbound on average chain length
  1483. DWORD initsize, // Initial size of hash table.
  1484. CLKRHashTable* phtParent // Owning table.
  1485. );
  1486. static bool
  1487. _FreeSubTable(
  1488. SubTable* plht);
  1489. public:
  1490. CLKRHashTable(
  1491. LPCSTR pszName, // An identifier for debugging
  1492. PFnExtractKey pfnExtractKey, // Extract key from record
  1493. PFnCalcKeyHash pfnCalcKeyHash, // Calculate hash signature of key
  1494. PFnEqualKeys pfnEqualKeys, // Compare two keys
  1495. PFnAddRefRecord pfnAddRefRecord,// AddRef in FindKey, etc
  1496. double maxload=LK_DFLT_MAXLOAD, // bound on avg chain length
  1497. DWORD initsize=LK_DFLT_INITSIZE, // Initial size of hash table.
  1498. DWORD num_subtbls=LK_DFLT_NUM_SUBTBLS // #subordinate hash tables.
  1499. );
  1500. ~CLKRHashTable();
  1501. static const char* ClassName() {return "CLKRHashTable";}
  1502. int NumSubTables() const {return m_cSubTables;}
  1503. static LK_TABLESIZE NumSubTables(DWORD& rinitsize, DWORD& rnum_subtbls);
  1504. // Thin wrappers for the corresponding methods in CLKRLinearHashTable
  1505. LK_RETCODE InsertRecord(const void* pvRecord, bool fOverwrite=false)
  1506. {
  1507. #ifdef LKR_SUBTABLE
  1508. if (!IsUsable())
  1509. return m_lkrcState;
  1510. if (pvRecord == NULL)
  1511. return LK_BAD_RECORD;
  1512. #endif
  1513. LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op
  1514. DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord));
  1515. SubTable* const pst = _SubTable(hash_val);
  1516. #ifdef LKR_SUBTABLE
  1517. LK_RETCODE lk = pst->_InsertRecord(pvRecord, hash_val, fOverwrite);
  1518. #else
  1519. LK_RETCODE lk = (pst != NULL
  1520. ? pst->_InsertRecord(pvRecord, hash_val,
  1521. fOverwrite)
  1522. : LK_UNUSABLE);
  1523. #endif
  1524. LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op
  1525. return lk;
  1526. }
  1527. LK_RETCODE DeleteKey(const DWORD_PTR pnKey)
  1528. {
  1529. #ifdef LKR_SUBTABLE
  1530. if (!IsUsable())
  1531. return m_lkrcState;
  1532. #endif
  1533. LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op
  1534. DWORD hash_val = _CalcKeyHash(pnKey);
  1535. SubTable* const pst = _SubTable(hash_val);
  1536. #ifdef LKR_SUBTABLE
  1537. LK_RETCODE lk = pst->_DeleteKey(pnKey, hash_val);
  1538. #else
  1539. LK_RETCODE lk = (pst != NULL
  1540. ? pst->_DeleteKey(pnKey, hash_val)
  1541. : LK_UNUSABLE);
  1542. #endif
  1543. LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op
  1544. return lk;
  1545. }
  1546. LK_RETCODE DeleteRecord(const void* pvRecord)
  1547. {
  1548. #ifdef LKR_SUBTABLE
  1549. if (!IsUsable())
  1550. return m_lkrcState;
  1551. if (pvRecord == NULL)
  1552. return LK_BAD_RECORD;
  1553. #endif
  1554. LKRHASH_GLOBAL_WRITE_LOCK(); // usu. no-op
  1555. DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord));
  1556. SubTable* const pst = _SubTable(hash_val);
  1557. #ifdef LKR_SUBTABLE
  1558. LK_RETCODE lk = pst->_DeleteRecord(pvRecord, hash_val);
  1559. #else
  1560. LK_RETCODE lk = (pst != NULL
  1561. ? pst->_DeleteRecord(pvRecord, hash_val)
  1562. : LK_UNUSABLE);
  1563. #endif
  1564. LKRHASH_GLOBAL_WRITE_UNLOCK(); // usu. no-op
  1565. return lk;
  1566. }
  1567. LK_RETCODE FindKey(const DWORD_PTR pnKey,
  1568. const void** ppvRecord) const
  1569. {
  1570. #ifdef LKR_SUBTABLE
  1571. if (!IsUsable())
  1572. return m_lkrcState;
  1573. if (ppvRecord == NULL)
  1574. return LK_BAD_RECORD;
  1575. #endif
  1576. LKRHASH_GLOBAL_READ_LOCK(); // usu. no-op
  1577. DWORD hash_val = _CalcKeyHash(pnKey);
  1578. SubTable* const pst = _SubTable(hash_val);
  1579. #ifdef LKR_SUBTABLE
  1580. LK_RETCODE lkrc = pst->_FindKey(pnKey, hash_val, ppvRecord);
  1581. #else
  1582. LK_RETCODE lkrc = (pst != NULL
  1583. ? pst->_FindKey(pnKey, hash_val, ppvRecord)
  1584. : LK_UNUSABLE);
  1585. #endif
  1586. LKRHASH_GLOBAL_READ_UNLOCK(); // usu. no-op
  1587. return lkrc;
  1588. }
  1589. LK_RETCODE FindRecord(const void* pvRecord) const
  1590. {
  1591. #ifdef LKR_SUBTABLE
  1592. if (!IsUsable())
  1593. return m_lkrcState;
  1594. if (pvRecord == NULL)
  1595. return LK_BAD_RECORD;
  1596. #endif
  1597. LKRHASH_GLOBAL_READ_LOCK(); // usu. no-op
  1598. DWORD hash_val = _CalcKeyHash(_ExtractKey(pvRecord));
  1599. SubTable* const pst = _SubTable(hash_val);
  1600. #ifdef LKR_SUBTABLE
  1601. LK_RETCODE lkrc = pst->_FindRecord(pvRecord, hash_val);
  1602. #else
  1603. LK_RETCODE lkrc = (pst != NULL
  1604. ? pst->_FindRecord(pvRecord, hash_val)
  1605. : LK_UNUSABLE);
  1606. #endif
  1607. LKRHASH_GLOBAL_READ_UNLOCK(); // usu. no-op
  1608. return lkrc;
  1609. }
  1610. DWORD Apply(PFnRecordAction pfnAction,
  1611. void* pvState=NULL,
  1612. LK_LOCKTYPE lkl=LKL_READLOCK);
  1613. DWORD ApplyIf(PFnRecordPred pfnPredicate,
  1614. PFnRecordAction pfnAction,
  1615. void* pvState=NULL,
  1616. LK_LOCKTYPE lkl=LKL_READLOCK);
  1617. DWORD DeleteIf(PFnRecordPred pfnPredicate,
  1618. void* pvState=NULL);
  1619. void Clear();
  1620. int CheckTable() const;
  1621. void Print() const;
  1622. DWORD Size() const;
  1623. DWORD MaxSize() const;
  1624. CLKRHashTableStats GetStatistics() const;
  1625. bool IsValid() const;
  1626. void SetTableLockSpinCount(WORD wSpins);
  1627. WORD GetTableLockSpinCount();
  1628. void SetBucketLockSpinCount(WORD wSpins);
  1629. WORD GetBucketLockSpinCount();
  1630. enum {
  1631. SIGNATURE = (('L') | ('K' << 8) | ('H' << 16) | ('T' << 24)),
  1632. SIGNATURE_FREE = (('L') | ('K' << 8) | ('H' << 16) | ('x' << 24)),
  1633. };
  1634. bool
  1635. ValidSignature() const
  1636. { return m_dwSignature == SIGNATURE;}
  1637. // Is the hash table usable?
  1638. bool IsUsable() const
  1639. { return (m_lkrcState == LK_SUCCESS); }
  1640. void WriteLock();
  1641. void ReadLock() const;
  1642. void WriteUnlock() const;
  1643. void ReadUnlock() const;
  1644. bool IsWriteLocked() const;
  1645. bool IsReadLocked() const;
  1646. bool IsWriteUnlocked() const;
  1647. bool IsReadUnlocked() const;
  1648. // LKRHASH_ALLOCATOR_DEFINITIONS(CLKRHashTable);
  1649. public:
  1650. typedef SubTable::CIterator CLHTIterator;
  1651. class CIterator : public CLHTIterator
  1652. {
  1653. protected:
  1654. friend class CLKRHashTable;
  1655. CLKRHashTable* m_pht; // which hash table?
  1656. int m_ist; // which subtable
  1657. private:
  1658. // Private copy ctor and op= to prevent compiler synthesizing them.
  1659. // Must provide (bad) implementation because we export instantiations.
  1660. CIterator(const CIterator&) {*(BYTE*)NULL;}
  1661. CIterator& operator=(const CIterator&) {return *(CIterator*)NULL;}
  1662. public:
  1663. CIterator(
  1664. LK_LOCKTYPE lkl=LKL_WRITELOCK)
  1665. : CLHTIterator(lkl),
  1666. m_pht(NULL),
  1667. m_ist(-1)
  1668. {}
  1669. const void* Record() const
  1670. {
  1671. IRTLASSERT(IsValid());
  1672. // This is a hack to work around a compiler bug. Calling
  1673. // CLHTIterator::Record calls this function recursively until
  1674. // the stack overflows.
  1675. const CLHTIterator* pBase = static_cast<const CLHTIterator*>(this);
  1676. return pBase->Record();
  1677. }
  1678. const DWORD_PTR Key() const
  1679. {
  1680. IRTLASSERT(IsValid());
  1681. const CLHTIterator* pBase = static_cast<const CLHTIterator*>(this);
  1682. return pBase->Key();
  1683. }
  1684. bool IsValid() const
  1685. {
  1686. const CLHTIterator* pBase = static_cast<const CLHTIterator*>(this);
  1687. return (m_pht != NULL && m_ist >= 0 && pBase->IsValid());
  1688. }
  1689. };
  1690. // Const iterators for readonly access
  1691. class CConstIterator : public CIterator
  1692. {
  1693. private:
  1694. // Private, unimplemented copy ctor and op= to prevent
  1695. // compiler synthesizing them.
  1696. CConstIterator(const CConstIterator&);
  1697. CConstIterator& operator=(const CConstIterator&);
  1698. public:
  1699. CConstIterator()
  1700. : CIterator(LKL_READLOCK)
  1701. {}
  1702. };
  1703. public:
  1704. LK_RETCODE InitializeIterator(CIterator* piter);
  1705. LK_RETCODE IncrementIterator(CIterator* piter);
  1706. LK_RETCODE CloseIterator(CIterator* piter);
  1707. LK_RETCODE InitializeIterator(CConstIterator* piter) const
  1708. {
  1709. IRTLASSERT(piter != NULL && piter->m_pht == NULL);
  1710. IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
  1711. if (piter == NULL || piter->m_pht != NULL
  1712. || piter->m_lkl == LKL_WRITELOCK)
  1713. return LK_BAD_ITERATOR;
  1714. return const_cast<CLKRHashTable*>(this)
  1715. ->InitializeIterator(static_cast<CIterator*>(piter));
  1716. }
  1717. LK_RETCODE IncrementIterator(CConstIterator* piter) const
  1718. {
  1719. IRTLASSERT(piter != NULL && piter->m_pht == this);
  1720. IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
  1721. if (piter == NULL || piter->m_pht != this
  1722. || piter->m_lkl == LKL_WRITELOCK)
  1723. return LK_BAD_ITERATOR;
  1724. return const_cast<CLKRHashTable*>(this)
  1725. ->IncrementIterator(static_cast<CIterator*>(piter));
  1726. }
  1727. LK_RETCODE CloseIterator(CConstIterator* piter) const
  1728. {
  1729. IRTLASSERT(piter != NULL && piter->m_pht == this);
  1730. IRTLASSERT(piter->m_lkl != LKL_WRITELOCK);
  1731. if (piter == NULL || piter->m_pht != this
  1732. || piter->m_lkl == LKL_WRITELOCK)
  1733. return LK_BAD_ITERATOR;
  1734. return const_cast<CLKRHashTable*>(this)
  1735. ->CloseIterator(static_cast<CIterator*>(piter));
  1736. }
  1737. };
  1738. //--------------------------------------------------------------------
  1739. // A typesafe wrapper for CLKRHashTable (or CLKRLinearHashTable).
  1740. //
  1741. // * _Derived must derive from CTypedHashTable and provide certain member
  1742. // functions. It's needed for various downcasting operations. See
  1743. // CStringTestHashTable and CNumberTestHashTable below.
  1744. // * _Record is the type of the record. C{Linear}HashTable will store
  1745. // pointers to _Record.
  1746. // * _Key is the type of the key. _Key is used directly; i.e., it is
  1747. // not assumed to be a pointer type. C{Linear}HashTable assumes that
  1748. // the key is stored in the associated record. See the comments
  1749. // at the declaration of PFnExtractKey for more details.
  1750. //
  1751. // (optional parameters):
  1752. // * _BaseHashTable is the base hash table: CLKRHashTable or
  1753. /// CLKRLinearHashTable
  1754. // * _BaseIterator is the iterator type, _BaseHashTable::CIterator
  1755. //
  1756. // CTypedHashTable could derive directly from CLKRLinearHashTable, if you
  1757. // don't need the extra overhead of CLKRHashTable (which is quite low).
  1758. //
  1759. // You may need to add the following line to your code to disable
  1760. // warning messages about truncating extremly long identifiers.
  1761. // #pragma warning (disable : 4786)
  1762. //--------------------------------------------------------------------
  1763. #define LKRHASH_HACKY_CAST(T, pv) ((T) (UINT_PTR) (pv))
  1764. template < class _Derived, class _Record, class _Key,
  1765. class _BaseHashTable=CLKRHashTable,
  1766. class _BaseIterator=_BaseHashTable::CIterator
  1767. >
  1768. class CTypedHashTable : public _BaseHashTable
  1769. {
  1770. public:
  1771. // convenient aliases
  1772. typedef _Derived Derived;
  1773. typedef _Record Record;
  1774. typedef _Key Key;
  1775. typedef _BaseHashTable BaseHashTable;
  1776. typedef CTypedHashTable<_Derived, _Record, _Key,
  1777. _BaseHashTable, _BaseIterator>
  1778. HashTable;
  1779. typedef _BaseIterator BaseIterator;
  1780. // ApplyIf() and DeleteIf(): Does the record match the predicate?
  1781. // Note: takes a Record*, not a const Record*. You can modify the
  1782. // record in Pred() or Action(), if you like, but if you do, you
  1783. // should use LKL_WRITELOCK to lock the table.
  1784. typedef LK_PREDICATE (WINAPI *PFnRecordPred) (Record* pRec, void* pvState);
  1785. // Apply() et al: Perform action on record.
  1786. typedef LK_ACTION (WINAPI *PFnRecordAction)(Record* pRec, void* pvState);
  1787. private:
  1788. // Wrappers for the typesafe methods exposed by the derived class
  1789. static const DWORD_PTR
  1790. _ExtractKey(const void* pvRecord)
  1791. {
  1792. const _Record* pRec = static_cast<const _Record*>(pvRecord);
  1793. _Key key = static_cast<_Key>(_Derived::ExtractKey(pRec));
  1794. // I would prefer to use reinterpret_cast here, but the stupid
  1795. // Win64 compiler thinks it knows better than I do.
  1796. return (const DWORD_PTR) key;
  1797. }
  1798. static DWORD
  1799. _CalcKeyHash(const DWORD_PTR pnKey)
  1800. {
  1801. _Key key = LKRHASH_HACKY_CAST(_Key, pnKey);
  1802. return _Derived::CalcKeyHash(key);
  1803. }
  1804. static bool
  1805. _EqualKeys(const DWORD_PTR pnKey1, const DWORD_PTR pnKey2)
  1806. {
  1807. _Key key1 = LKRHASH_HACKY_CAST(_Key, pnKey1);
  1808. _Key key2 = LKRHASH_HACKY_CAST(_Key, pnKey2);
  1809. return _Derived::EqualKeys(key1, key2);
  1810. }
  1811. static void
  1812. _AddRefRecord(const void* pvRecord, int nIncr)
  1813. {
  1814. _Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
  1815. _Derived::AddRefRecord(pRec, nIncr);
  1816. }
  1817. // Typesafe wrappers for Apply, ApplyIf, and DeleteIf.
  1818. class CState
  1819. {
  1820. public:
  1821. PFnRecordPred m_pfnPred;
  1822. PFnRecordAction m_pfnAction;
  1823. void* m_pvState;
  1824. CState(
  1825. PFnRecordPred pfnPred,
  1826. PFnRecordAction pfnAction,
  1827. void* pvState)
  1828. : m_pfnPred(pfnPred), m_pfnAction(pfnAction), m_pvState(pvState)
  1829. {}
  1830. };
  1831. static LK_PREDICATE
  1832. _Pred(const void* pvRecord, void* pvState)
  1833. {
  1834. _Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
  1835. CState* pState = static_cast<CState*>(pvState);
  1836. return (*pState->m_pfnPred)(pRec, pState->m_pvState);
  1837. }
  1838. static LK_ACTION
  1839. _Action(const void* pvRecord, void* pvState)
  1840. {
  1841. _Record* pRec = static_cast<_Record*>(const_cast<void*>(pvRecord));
  1842. CState* pState = static_cast<CState*>(pvState);
  1843. return (*pState->m_pfnAction)(pRec, pState->m_pvState);
  1844. }
  1845. public:
  1846. CTypedHashTable(
  1847. LPCSTR pszName, // An identifier for debugging
  1848. double maxload=LK_DFLT_MAXLOAD, // Upperbound on avg chain length
  1849. DWORD initsize=LK_DFLT_INITSIZE, // Initial size of hash table.
  1850. DWORD num_subtbls=LK_DFLT_NUM_SUBTBLS// #subordinate hash tables.
  1851. )
  1852. : _BaseHashTable(pszName, _ExtractKey, _CalcKeyHash, _EqualKeys,
  1853. _AddRefRecord, maxload, initsize, num_subtbls)
  1854. {}
  1855. LK_RETCODE InsertRecord(const _Record* pRec, bool fOverwrite=false)
  1856. { return _BaseHashTable::InsertRecord(pRec, fOverwrite); }
  1857. LK_RETCODE DeleteKey(const _Key key)
  1858. { return _BaseHashTable::DeleteKey(reinterpret_cast<const DWORD_PTR>(key));}
  1859. LK_RETCODE DeleteRecord(const _Record* pRec)
  1860. { return _BaseHashTable::DeleteRecord(pRec);}
  1861. // Note: returns a _Record**, not a const Record**. Note that you
  1862. // can use a const type for the template parameter to ensure constness.
  1863. LK_RETCODE FindKey(const _Key key, _Record** ppRec) const
  1864. {
  1865. if (ppRec == NULL)
  1866. return LK_BAD_RECORD;
  1867. *ppRec = NULL;
  1868. const void* pvRec = NULL;
  1869. const void* pvKey = reinterpret_cast<const void*>((DWORD_PTR)(key));
  1870. DWORD_PTR pnKey = reinterpret_cast<DWORD_PTR>(pvKey);
  1871. LK_RETCODE lkrc = _BaseHashTable::FindKey(pnKey, &pvRec);
  1872. *ppRec = static_cast<_Record*>(const_cast<void*>(pvRec));
  1873. return lkrc;
  1874. }
  1875. LK_RETCODE FindRecord(const _Record* pRec) const
  1876. { return _BaseHashTable::FindRecord(pRec);}
  1877. // Other C{Linear}HashTable methods can be exposed without change
  1878. // TODO: Print?
  1879. // Typesafe wrappers for Apply et al
  1880. DWORD Apply(PFnRecordAction pfnAction,
  1881. void* pvState=NULL,
  1882. LK_LOCKTYPE lkl=LKL_READLOCK)
  1883. {
  1884. IRTLASSERT(pfnAction != NULL);
  1885. if (pfnAction == NULL)
  1886. return 0;
  1887. CState state(NULL, pfnAction, pvState);
  1888. return _BaseHashTable::Apply(_Action, &state, lkl);
  1889. }
  1890. DWORD ApplyIf(PFnRecordPred pfnPredicate,
  1891. PFnRecordAction pfnAction,
  1892. void* pvState=NULL,
  1893. LK_LOCKTYPE lkl=LKL_READLOCK)
  1894. {
  1895. IRTLASSERT(pfnPredicate != NULL && pfnAction != NULL);
  1896. if (pfnPredicate == NULL || pfnAction == NULL)
  1897. return 0;
  1898. CState state(pfnPredicate, pfnAction, pvState);
  1899. return _BaseHashTable::ApplyIf(_Pred, _Action, &state, lkl);
  1900. }
  1901. DWORD DeleteIf(PFnRecordPred pfnPredicate, void* pvState=NULL)
  1902. {
  1903. IRTLASSERT(pfnPredicate != NULL);
  1904. if (pfnPredicate == NULL)
  1905. return 0;
  1906. CState state(pfnPredicate, NULL, pvState);
  1907. return _BaseHashTable::DeleteIf(_Pred, &state);
  1908. }
  1909. // Typesafe wrappers for iterators
  1910. class CIterator : public _BaseIterator
  1911. {
  1912. private:
  1913. // Private, unimplemented copy ctor and op= to prevent
  1914. // compiler synthesizing them.
  1915. CIterator(const CIterator&);
  1916. CIterator& operator=(const CIterator&);
  1917. public:
  1918. CIterator(
  1919. LK_LOCKTYPE lkl=LKL_WRITELOCK)
  1920. : _BaseIterator(lkl)
  1921. {}
  1922. _Record* Record() const
  1923. {
  1924. const _BaseIterator* pBase = static_cast<const _BaseIterator*>(this);
  1925. return reinterpret_cast<_Record*>(const_cast<void*>(
  1926. pBase->Record()));
  1927. }
  1928. _Key Key() const
  1929. {
  1930. const _BaseIterator* pBase = static_cast<const _BaseIterator*>(this);
  1931. return reinterpret_cast<_Key>(reinterpret_cast<void*>(pBase->Key()));
  1932. }
  1933. };
  1934. // readonly iterator
  1935. class CConstIterator : public CIterator
  1936. {
  1937. private:
  1938. // Private, unimplemented copy ctor and op= to prevent
  1939. // compiler synthesizing them.
  1940. CConstIterator(const CConstIterator&);
  1941. CConstIterator& operator=(const CConstIterator&);
  1942. public:
  1943. CConstIterator()
  1944. : CIterator(LKL_READLOCK)
  1945. {}
  1946. const _Record* Record() const
  1947. {
  1948. return CIterator::Record();
  1949. }
  1950. const _Key Key() const
  1951. {
  1952. return CIterator::Key();
  1953. }
  1954. };
  1955. public:
  1956. LK_RETCODE InitializeIterator(CIterator* piter)
  1957. {
  1958. return _BaseHashTable::InitializeIterator(piter);
  1959. }
  1960. LK_RETCODE IncrementIterator(CIterator* piter)
  1961. {
  1962. return _BaseHashTable::IncrementIterator(piter);
  1963. }
  1964. LK_RETCODE CloseIterator(CIterator* piter)
  1965. {
  1966. return _BaseHashTable::CloseIterator(piter);
  1967. }
  1968. LK_RETCODE InitializeIterator(CConstIterator* piter) const
  1969. {
  1970. return const_cast<HashTable*>(this)
  1971. ->InitializeIterator(static_cast<CIterator*>(piter));
  1972. }
  1973. LK_RETCODE IncrementIterator(CConstIterator* piter) const
  1974. {
  1975. return const_cast<HashTable*>(this)
  1976. ->IncrementIterator(static_cast<CIterator*>(piter));
  1977. }
  1978. LK_RETCODE CloseIterator(CConstIterator* piter) const
  1979. {
  1980. return const_cast<HashTable*>(this)
  1981. ->CloseIterator(static_cast<CIterator*>(piter));
  1982. }
  1983. };
  1984. #ifdef __LKRHASH_NAMESPACE__
  1985. }
  1986. #endif // __LKRHASH_NAMESPACE__
  1987. #endif // __LKRHASH_H__
  1988. #ifdef SAMPLE_LKRHASH_TESTCLASS
  1989. //--------------------------------------------------------------------
  1990. // An example of how to create a wrapper for CLKRHashTable
  1991. //--------------------------------------------------------------------
  1992. // some random class
  1993. class CTest
  1994. {
  1995. public:
  1996. enum {BUFFSIZE=20};
  1997. int m_n; // This will also be a key
  1998. char m_sz[BUFFSIZE]; // This will be the primary key
  1999. bool m_fWhatever;
  2000. mutable LONG m_cRefs; // Reference count for lifetime management.
  2001. // Must be mutable to use 'const CTest*' in
  2002. // hashtables
  2003. CTest(int n, const char* psz, bool f)
  2004. : m_n(n), m_fWhatever(f), m_cRefs(0)
  2005. {
  2006. strncpy(m_sz, psz, BUFFSIZE-1);
  2007. m_sz[BUFFSIZE-1] = '\0';
  2008. }
  2009. ~CTest()
  2010. {
  2011. IRTLASSERT(m_cRefs == 0);
  2012. }
  2013. };
  2014. // A typed hash table of CTests, keyed on the string field. Case-insensitive.
  2015. class CStringTestHashTable
  2016. : public CTypedHashTable<CStringTestHashTable, const CTest, const char*>
  2017. {
  2018. public:
  2019. CStringTestHashTable()
  2020. : CTypedHashTable<CStringTestHashTable, const CTest,
  2021. const char*>("string", LK_DFLT_MAXLOAD,
  2022. LK_SMALL_TABLESIZE)
  2023. {}
  2024. static const char*
  2025. ExtractKey(const CTest* pTest)
  2026. {
  2027. return pTest->m_sz;
  2028. }
  2029. static DWORD
  2030. CalcKeyHash(const char* pszKey)
  2031. {
  2032. return HashStringNoCase(pszKey);
  2033. }
  2034. static bool
  2035. EqualKeys(const char* pszKey1, const char* pszKey2)
  2036. {
  2037. return _stricmp(pszKey1, pszKey2) == 0;
  2038. }
  2039. static void
  2040. AddRefRecord(const CTest* pTest, int nIncr)
  2041. {
  2042. if (nIncr == +1)
  2043. {
  2044. // or, perhaps, pIFoo->AddRef() (watch out for marshalling)
  2045. // or ++pTest->m_cRefs (single-threaded only)
  2046. InterlockedIncrement(&pTest->m_cRefs);
  2047. }
  2048. else if (nIncr == -1)
  2049. {
  2050. // or, perhaps, pIFoo->Release() or --pTest->m_cRefs;
  2051. LONG l = InterlockedDecrement(&pTest->m_cRefs);
  2052. // For some hashtables, it may also make sense to add the following
  2053. // if (l == 0) delete pTest;
  2054. // but that would typically only apply when InsertRecord was
  2055. // used thus
  2056. // lkrc = ht.InsertRecord(new CTest(foo, bar));
  2057. }
  2058. else
  2059. IRTLASSERT(0);
  2060. TRACE("AddRef(%p, %s) %d, cRefs == %d\n",
  2061. pTest, pTest->m_sz, nIncr, pTest->m_cRefs);
  2062. }
  2063. };
  2064. // Another typed hash table of CTests. This one is keyed on the numeric field.
  2065. class CNumberTestHashTable
  2066. : public CTypedHashTable<CNumberTestHashTable, const CTest, int>
  2067. {
  2068. public:
  2069. CNumberTestHashTable()
  2070. : CTypedHashTable<CNumberTestHashTable, const CTest, int>("number") {}
  2071. static int ExtractKey(const CTest* pTest) {return pTest->m_n;}
  2072. static DWORD CalcKeyHash(int nKey) {return Hash(nKey);}
  2073. static bool EqualKeys(int nKey1, int nKey2) {return nKey1 == nKey2;}
  2074. static void AddRefRecord(const CTest* pTest, int nIncr) {/* do nothing*/}
  2075. };
  2076. // A class to exercise ApplyIf()
  2077. class CApplyIfTest
  2078. {
  2079. public:
  2080. static LK_PREDICATE
  2081. Predicate(const CTest* pTest, void* pvState)
  2082. {
  2083. CApplyIfTest* pThis = static_cast<CApplyIfTest*>(pvState);
  2084. ++pThis->m_cPreds;
  2085. TRACE("CApplyIfTest::Predicate(%p (%s, %d), %p)\n",
  2086. pTest, pTest->m_sz, pTest->m_n, pThis);
  2087. return (pTest->m_n % 10 == 7) ? LKP_PERFORM : LKP_NO_ACTION;
  2088. }
  2089. static LK_ACTION
  2090. Action(const CTest* pTest, void* pvState)
  2091. {
  2092. CApplyIfTest* pThis = static_cast<CApplyIfTest*>(pvState);
  2093. ++pThis->m_cActions;
  2094. LK_ACTION lka = (pTest->m_n > 30) ? LKA_SUCCEEDED : LKA_FAILED;
  2095. TRACE("CApplyIfTest::Action(%p (%s, %d), %p) %s\n",
  2096. pTest, pTest->m_sz, pTest->m_n, pThis,
  2097. lka == LKA_SUCCEEDED ? "succeeded" : "failed");
  2098. if (lka == LKA_SUCCEEDED)
  2099. ++pThis->m_cSuccesses;
  2100. else if (lka == LKA_FAILED)
  2101. ++pThis->m_cFailures;
  2102. return lka;
  2103. }
  2104. int m_cPreds;
  2105. int m_cActions;
  2106. int m_cSuccesses;
  2107. int m_cFailures;
  2108. CApplyIfTest()
  2109. : m_cPreds(0), m_cActions(0), m_cSuccesses(0), m_cFailures(0)
  2110. {}
  2111. };
  2112. // The Predicate and Action functions can be static member functions,
  2113. // but don't have to be
  2114. LK_PREDICATE
  2115. DeleteIfGt10(
  2116. const CTest* pTest,
  2117. void* pvState)
  2118. {
  2119. TRACE("DeleteIfGt10(%p, %s, %p) = %d\n",
  2120. pTest, pTest->m_sz, pvState, pTest->m_n);
  2121. return (pTest->m_n > 10) ? LKP_PERFORM : LKP_NO_ACTION;
  2122. }
  2123. #include <stdio.h>
  2124. #include <string.h>
  2125. void Test(
  2126. bool fVerbose)
  2127. {
  2128. // Some objects for the hash tables
  2129. CTest tl(5, "Larson", true);
  2130. CTest tk(17, "Krishnan", false);
  2131. CTest tr(37, "Reilly", true);
  2132. // A string-keyed hash table
  2133. CStringTestHashTable stht;
  2134. IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tl));
  2135. IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tk));
  2136. IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tr));
  2137. TRACE("Check the overwrite feature of InsertRecord\n");
  2138. IRTLVERIFY(LK_KEY_EXISTS == stht.InsertRecord(&tr, false));
  2139. IRTLASSERT(tr.m_cRefs == 1);
  2140. IRTLVERIFY(LK_SUCCESS == stht.InsertRecord(&tr, true));
  2141. IRTLASSERT(tr.m_cRefs == 1); // 1+1-1 == 1
  2142. TRACE("Check that the keys are really present in the table and that "
  2143. "the refcounting works\n");
  2144. const CTest* pTest = NULL;
  2145. IRTLVERIFY(LK_SUCCESS == stht.FindKey(tl.m_sz, &pTest) && pTest == &tl);
  2146. IRTLASSERT(tl.m_cRefs == 2);
  2147. IRTLVERIFY(LK_SUCCESS == stht.FindKey(tk.m_sz, &pTest) && pTest == &tk);
  2148. IRTLASSERT(tk.m_cRefs == 2);
  2149. IRTLVERIFY(LK_SUCCESS == stht.FindKey(tr.m_sz, &pTest) && pTest == &tr);
  2150. IRTLASSERT(tr.m_cRefs == 2);
  2151. IRTLVERIFY(LK_SUCCESS == stht.FindRecord(&tr));
  2152. IRTLASSERT(tr.m_cRefs == 2); // FindRecord does not addref
  2153. TRACE("Look for a key under an alternate spelling (case-insensitive)\n");
  2154. IRTLVERIFY(LK_SUCCESS == stht.FindKey("rEiLlY", &pTest) && pTest == &tr);
  2155. IRTLASSERT(tr.m_cRefs == 3);
  2156. TRACE("Release the references added by FindKey\n");
  2157. stht.AddRefRecord(&tl, -1);
  2158. tk.m_cRefs--;
  2159. tr.m_cRefs = 1;
  2160. TRACE("Now build the numeric hash table\n");
  2161. CNumberTestHashTable ntht;
  2162. IRTLVERIFY(LK_SUCCESS == ntht.InsertRecord(&tl));
  2163. IRTLVERIFY(LK_SUCCESS == ntht.InsertRecord(&tk));
  2164. IRTLVERIFY(LK_SUCCESS == ntht.InsertRecord(&tr));
  2165. TRACE("Test ApplyIf()\n");
  2166. CApplyIfTest ait;
  2167. IRTLVERIFY(1 == ntht.ApplyIf(ait.Predicate, ait.Action, &ait));
  2168. IRTLASSERT(3 == ait.m_cPreds && 2 == ait.m_cActions
  2169. && 1 == ait.m_cSuccesses && 1 == ait.m_cFailures);
  2170. TRACE("Test DeleteIf()\n");
  2171. IRTLASSERT(3 == ntht.Size());
  2172. ntht.DeleteIf(DeleteIfGt10, NULL);
  2173. IRTLASSERT(1 == ntht.Size());
  2174. TRACE("Check that the keys that were supposed to be deleted "
  2175. "really are gone\n");
  2176. IRTLASSERT(tl.m_n <= 10);
  2177. IRTLVERIFY(LK_SUCCESS == ntht.FindKey(tl.m_n, &pTest) && pTest == &tl);
  2178. IRTLASSERT(tk.m_n > 10);
  2179. IRTLVERIFY(LK_NO_SUCH_KEY == ntht.FindKey(tk.m_n, &pTest)
  2180. && pTest == NULL);
  2181. IRTLASSERT(tr.m_n > 10);
  2182. IRTLVERIFY(LK_NO_SUCH_KEY == ntht.FindKey(tr.m_n, &pTest)
  2183. && pTest == NULL);
  2184. IRTLVERIFY(LK_SUCCESS == ntht.DeleteRecord(&tl));
  2185. IRTLASSERT(0 == ntht.Size());
  2186. TRACE("Check Iterators\n");
  2187. DWORD cRec = 0;
  2188. CStringTestHashTable::CIterator iter;
  2189. LK_RETCODE lkrc = stht.InitializeIterator(&iter);
  2190. while (lkrc == LK_SUCCESS)
  2191. {
  2192. ++cRec;
  2193. CStringTestHashTable::Key pszKey = iter.Key();
  2194. CStringTestHashTable::Record* pRec = iter.Record();
  2195. IRTLASSERT(pRec == &tl || pRec == &tk || pRec == &tr);
  2196. if (fVerbose)
  2197. printf("Record(%p) contains \"%s\"\n", pRec, pszKey);
  2198. lkrc = stht.IncrementIterator(&iter);
  2199. }
  2200. IRTLASSERT(lkrc == LK_NO_MORE_ELEMENTS);
  2201. lkrc = stht.CloseIterator(&iter);
  2202. IRTLASSERT(lkrc == LK_SUCCESS);
  2203. IRTLASSERT(cRec == stht.Size());
  2204. TRACE("Check const iterators\n");
  2205. const CStringTestHashTable& sthtConst = stht;
  2206. CStringTestHashTable::CConstIterator iterConst;
  2207. cRec = 0;
  2208. lkrc = sthtConst.InitializeIterator(&iterConst);
  2209. while (lkrc == LK_SUCCESS)
  2210. {
  2211. ++cRec;
  2212. const CStringTestHashTable::Key pszKey = iterConst.Key();
  2213. const CStringTestHashTable::Record* pRec = iterConst.Record();
  2214. IRTLASSERT(pRec == &tl || pRec == &tk || pRec == &tr);
  2215. if (fVerbose)
  2216. printf("Const Record(%p) contains \"%s\"\n", pRec, pszKey);
  2217. lkrc = sthtConst.IncrementIterator(&iterConst);
  2218. }
  2219. IRTLASSERT(lkrc == LK_NO_MORE_ELEMENTS);
  2220. lkrc = sthtConst.CloseIterator(&iterConst);
  2221. IRTLASSERT(lkrc == LK_SUCCESS);
  2222. IRTLASSERT(cRec == sthtConst.Size());
  2223. #if 1
  2224. TRACE("Check Clear\n");
  2225. stht.Clear();
  2226. IRTLASSERT(0 == stht.Size());
  2227. #else
  2228. TRACE("Check DeleteKey\n");
  2229. IRTLVERIFY(LK_SUCCESS == stht.DeleteKey(tl.m_sz));
  2230. IRTLVERIFY(LK_SUCCESS == stht.DeleteKey(tk.m_sz));
  2231. IRTLVERIFY(LK_SUCCESS == stht.DeleteKey(tr.m_sz));
  2232. #endif
  2233. TRACE("Test done\n");
  2234. // ~CTest will check for m_cRefs==0
  2235. }
  2236. #endif // SAMPLE_LKRHASH_TESTCLASS