SimpleHashTable.h 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #pragma once
  6. template<typename TKey, typename TData>
  7. struct SimpleHashEntry {
  8. TKey key;
  9. TData value;
  10. SimpleHashEntry *next;
  11. };
  12. // Size should be a power of 2 for optimal performance
  13. template<
  14. typename TKey,
  15. typename TData,
  16. typename TAllocator = ArenaAllocator,
  17. template <typename DataOrKey> class Comparer = DefaultComparer,
  18. bool resize = false,
  19. typename SizePolicy = PowerOf2Policy>
  20. class SimpleHashTable
  21. {
  22. typedef SimpleHashEntry<TKey, TData> EntryType;
  23. // REVIEW: Consider 5 or 7 as multiplication of these might be faster.
  24. static const int MaxAverageChainLength = 6;
  25. TAllocator *allocator;
  26. EntryType **table;
  27. EntryType *free;
  28. uint count;
  29. uint size;
  30. uint freecount;
  31. bool disableResize;
  32. #if PROFILE_DICTIONARY
  33. DictionaryStats *stats;
  34. #endif
  35. public:
  36. SimpleHashTable(TAllocator *allocator) :
  37. allocator(allocator),
  38. count(0),
  39. freecount(0)
  40. {
  41. this->size = SizePolicy::GetSize(64);
  42. Initialize();
  43. }
  44. SimpleHashTable(uint size, TAllocator* allocator) :
  45. allocator(allocator),
  46. count(0),
  47. freecount(0)
  48. {
  49. this->size = SizePolicy::GetSize(size);
  50. Initialize();
  51. }
  52. void Initialize()
  53. {
  54. disableResize = false;
  55. free = nullptr;
  56. table = AllocatorNewArrayZ(TAllocator, allocator, EntryType*, size);
  57. #if PROFILE_DICTIONARY
  58. stats = DictionaryStats::Create(typeid(this).name(), size);
  59. #endif
  60. }
  61. ~SimpleHashTable()
  62. {
  63. for (uint i = 0; i < size; i++)
  64. {
  65. EntryType * entry = table[i];
  66. while (entry != nullptr)
  67. {
  68. EntryType * next = entry->next;
  69. AllocatorDelete(TAllocator, allocator, entry);
  70. entry = next;
  71. }
  72. }
  73. while(free)
  74. {
  75. EntryType* current = free;
  76. free = current->next;
  77. AllocatorDelete(TAllocator, allocator, current);
  78. }
  79. AllocatorDeleteArray(TAllocator, allocator, size, table);
  80. }
  81. void DisableResize()
  82. {
  83. Assert(!resize || !disableResize);
  84. disableResize = true;
  85. }
  86. void EnableResize()
  87. {
  88. Assert(!resize || disableResize);
  89. disableResize = false;
  90. }
  91. void Set(TKey key, TData data)
  92. {
  93. EntryType* entry = FindOrAddEntry(key);
  94. entry->value = data;
  95. }
  96. bool Add(TKey key, TData data)
  97. {
  98. uint targetBucket = HashKeyToBucket(key);
  99. if(FindEntry(key, targetBucket) != nullptr)
  100. {
  101. return false;
  102. }
  103. AddInternal(key, data, targetBucket);
  104. return true;
  105. }
  106. void ReplaceValue(TKey key,TData data)
  107. {
  108. EntryType *current = FindEntry(key);
  109. if (current != nullptr)
  110. {
  111. current->value = data;
  112. }
  113. }
  114. void Remove(TKey key)
  115. {
  116. Remove(key, nullptr);
  117. }
  118. void Remove(TKey key, TData* pOut)
  119. {
  120. uint val = HashKeyToBucket(key);
  121. EntryType **prev=&table[val];
  122. for (EntryType * current = *prev ; current != nullptr; current = current->next)
  123. {
  124. if (Comparer<TKey>::Equals(key, current->key))
  125. {
  126. *prev = current->next;
  127. if (pOut != nullptr)
  128. {
  129. (*pOut) = current->value;
  130. }
  131. count--;
  132. FreeEntry(current);
  133. #if PROFILE_DICTIONARY
  134. if (stats)
  135. stats->Remove(table[val] == nullptr);
  136. #endif
  137. break;
  138. }
  139. prev = &current->next;
  140. }
  141. }
  142. BOOL HasEntry(TKey key)
  143. {
  144. return (FindEntry(key) != nullptr);
  145. }
  146. uint Count() const
  147. {
  148. return(count);
  149. }
  150. // If density is a compile-time constant, then we can optimize (avoids division)
  151. // Sometimes the compiler can also make this optimization, but this way it is guaranteed.
  152. template< uint density > bool IsDenserThan() const
  153. {
  154. return count > (size * density);
  155. }
  156. TData Lookup(TKey key)
  157. {
  158. EntryType *current = FindEntry(key);
  159. if (current != nullptr)
  160. {
  161. return current->value;
  162. }
  163. return TData();
  164. }
  165. TData LookupIndex(int index)
  166. {
  167. EntryType *current;
  168. int j=0;
  169. for (uint i=0; i < size; i++)
  170. {
  171. for (current = table[i] ; current != nullptr; current = current->next)
  172. {
  173. if (j==index)
  174. {
  175. return current->value;
  176. }
  177. j++;
  178. }
  179. }
  180. return nullptr;
  181. }
  182. bool TryGetValue(TKey key, TData *dataReference)
  183. {
  184. EntryType *current = FindEntry(key);
  185. if (current != nullptr)
  186. {
  187. *dataReference = current->value;
  188. return true;
  189. }
  190. return false;
  191. }
  192. TData& GetReference(TKey key)
  193. {
  194. EntryType * current = FindOrAddEntry(key);
  195. return current->value;
  196. }
  197. TData * TryGetReference(TKey key)
  198. {
  199. EntryType * current = FindEntry(key);
  200. if (current != nullptr)
  201. {
  202. return &current->value;
  203. }
  204. return nullptr;
  205. }
  206. template <class Fn>
  207. void Map(Fn fn)
  208. {
  209. EntryType *current;
  210. for (uint i=0;i<size;i++) {
  211. for (current = table[i] ; current != nullptr; current = current->next) {
  212. fn(current->key,current->value);
  213. }
  214. }
  215. }
  216. template <class Fn>
  217. void MapAndRemoveIf(Fn fn)
  218. {
  219. for (uint i=0; i<size; i++)
  220. {
  221. EntryType ** prev = &table[i];
  222. while (EntryType * current = *prev)
  223. {
  224. if (fn(current->key,current->value))
  225. {
  226. *prev = current->next;
  227. FreeEntry(current);
  228. }
  229. else
  230. {
  231. prev = &current->next;
  232. }
  233. }
  234. }
  235. }
  236. private:
  237. uint HashKeyToBucket(TKey hashKey)
  238. {
  239. return HashKeyToBucket(hashKey, size);
  240. }
  241. uint HashKeyToBucket(TKey hashKey, int size)
  242. {
  243. uint hashCode = Comparer<TKey>::GetHashCode(hashKey);
  244. return SizePolicy::GetBucket(hashCode, size);
  245. }
  246. EntryType * FindEntry(TKey key)
  247. {
  248. uint targetBucket = HashKeyToBucket(key);
  249. return FindEntry(key, targetBucket);
  250. }
  251. EntryType * FindEntry(TKey key, uint targetBucket)
  252. {
  253. for (EntryType * current = table[targetBucket] ; current != nullptr; current = current->next)
  254. {
  255. if (Comparer<TKey>::Equals(key, current->key))
  256. {
  257. return current;
  258. }
  259. }
  260. return nullptr;
  261. }
  262. EntryType * FindOrAddEntry(TKey key)
  263. {
  264. uint targetBucket = HashKeyToBucket(key);
  265. EntryType * entry = FindEntry(key, targetBucket);
  266. if (entry == nullptr)
  267. {
  268. entry = AddInternal(key, TData(), targetBucket);
  269. }
  270. return entry;
  271. }
  272. void FreeEntry(EntryType* current)
  273. {
  274. if ( freecount < 10 )
  275. {
  276. current->key = nullptr;
  277. current->value = NULL;
  278. current->next = free;
  279. free = current;
  280. freecount++;
  281. }
  282. else
  283. {
  284. AllocatorDelete(TAllocator, allocator, current);
  285. }
  286. }
  287. EntryType* GetFreeEntry()
  288. {
  289. EntryType* retFree = free;
  290. if (nullptr == retFree )
  291. {
  292. retFree = AllocatorNewStruct(TAllocator, allocator, EntryType);
  293. }
  294. else
  295. {
  296. free = retFree->next;
  297. freecount--;
  298. }
  299. return retFree;
  300. }
  301. EntryType* AddInternal(TKey key, TData data, uint targetBucket)
  302. {
  303. if(resize && !disableResize && IsDenserThan<MaxAverageChainLength>())
  304. {
  305. Resize(SizePolicy::GetSize(size*2));
  306. // After resize - we will need to recalculate the bucket
  307. targetBucket = HashKeyToBucket(key);
  308. }
  309. EntryType* entry = GetFreeEntry();
  310. entry->key = key;
  311. entry->value = data;
  312. entry->next = table[targetBucket];
  313. table[targetBucket] = entry;
  314. count++;
  315. #if PROFILE_DICTIONARY
  316. uint depth = 0;
  317. for (EntryType * current = table[targetBucket] ; current != nullptr; current = current->next)
  318. {
  319. ++depth;
  320. }
  321. if (stats)
  322. stats->Insert(depth);
  323. #endif
  324. return entry;
  325. }
  326. void Resize(int newSize)
  327. {
  328. Assert(!this->disableResize);
  329. EntryType** newTable = AllocatorNewArrayZ(TAllocator, allocator, EntryType*, newSize);
  330. for (uint i=0; i < size; i++)
  331. {
  332. EntryType* current = table[i];
  333. while (current != nullptr)
  334. {
  335. int targetBucket = HashKeyToBucket(current->key, newSize);
  336. EntryType* next = current->next; // Cache the next pointer
  337. current->next = newTable[targetBucket];
  338. newTable[targetBucket] = current;
  339. current = next;
  340. }
  341. }
  342. AllocatorDeleteArray(TAllocator, allocator, this->size, this->table);
  343. this->size = newSize;
  344. this->table = newTable;
  345. #if PROFILE_DICTIONARY
  346. if (stats)
  347. {
  348. uint emptyBuckets = 0 ;
  349. for (uint i=0; i < size; i++)
  350. {
  351. if(table[i] == nullptr)
  352. {
  353. emptyBuckets++;
  354. }
  355. }
  356. stats->Resize(newSize, emptyBuckets);
  357. }
  358. #endif
  359. }
  360. };