LockFreeHashMap.inl 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #pragma once
  4. namespace JPH {
  5. template <class Key, class Value>
  6. void LockFreeHashMap<Key, Value>::Init(uint inObjectStoreSizeBytes, uint32 inMaxBuckets)
  7. {
  8. JPH_ASSERT(inMaxBuckets >= 4 && IsPowerOf2(inMaxBuckets));
  9. JPH_ASSERT(mObjectStore == nullptr);
  10. JPH_ASSERT(mBuckets == nullptr);
  11. mObjectStoreSizeBytes = inObjectStoreSizeBytes;
  12. mNumBuckets = inMaxBuckets;
  13. mMaxBuckets = inMaxBuckets;
  14. mObjectStore = new uint8 [inObjectStoreSizeBytes];
  15. mBuckets = new atomic<uint32> [inMaxBuckets];
  16. Clear();
  17. }
  18. template <class Key, class Value>
  19. LockFreeHashMap<Key, Value>::~LockFreeHashMap()
  20. {
  21. delete [] mObjectStore;
  22. delete [] mBuckets;
  23. }
  24. template <class Key, class Value>
  25. void LockFreeHashMap<Key, Value>::Clear()
  26. {
  27. // Reset write offset and number of key value pairs
  28. mWriteOffset = 0;
  29. mNumKeyValues = 0;
  30. // Reset buckets 4 at a time
  31. static_assert(sizeof(atomic<uint32>) == sizeof(uint32));
  32. UVec4 invalid_handle = UVec4::sReplicate(cInvalidHandle);
  33. uint32 *start = reinterpret_cast<uint32 *>(mBuckets), *end = start + mNumBuckets;
  34. JPH_ASSERT(IsAligned(start, 16));
  35. while (start < end)
  36. {
  37. invalid_handle.StoreInt4Aligned(start);
  38. start += 4;
  39. }
  40. }
  41. template <class Key, class Value>
  42. void LockFreeHashMap<Key, Value>::SetNumBuckets(uint32 inNumBuckets)
  43. {
  44. JPH_ASSERT(mNumKeyValues == 0);
  45. JPH_ASSERT(inNumBuckets <= mMaxBuckets);
  46. JPH_ASSERT(inNumBuckets >= 4 && IsPowerOf2(inNumBuckets));
  47. mNumBuckets = inNumBuckets;
  48. }
  49. template <class Key, class Value>
  50. template <class... Params>
  51. inline typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::Create(const Key &inKey, size_t inKeyHash, int inExtraBytes, Params &&... inConstructorParams)
  52. {
  53. // This is not a multi map, test the key hasn't been inserted yet
  54. JPH_ASSERT(Find(inKey, inKeyHash) == nullptr);
  55. // Calculate total size
  56. uint size = sizeof(KeyValue) + inExtraBytes;
  57. // Allocate entry in the cache
  58. uint32 write_offset = mWriteOffset.fetch_add(size);
  59. if (write_offset + size > mObjectStoreSizeBytes)
  60. return nullptr;
  61. ++mNumKeyValues;
  62. // Construct the key/value pair
  63. KeyValue *kv = reinterpret_cast<KeyValue *>(mObjectStore + write_offset);
  64. #ifdef _DEBUG
  65. memset(kv, 0xcd, size);
  66. #endif
  67. kv->mKey = inKey;
  68. new (&kv->mValue) Value(forward<Params>(inConstructorParams)...);
  69. // Get the offset to the first object from the bucket with corresponding hash
  70. atomic<uint32> &offset = mBuckets[inKeyHash & (mNumBuckets - 1)];
  71. // Add this entry as the first element in the linked list
  72. uint32 new_offset = uint32(reinterpret_cast<uint8 *>(kv) - mObjectStore);
  73. for (;;)
  74. {
  75. uint32 old_offset = offset;
  76. kv->mNextOffset = old_offset;
  77. if (offset.compare_exchange_strong(old_offset, new_offset))
  78. break;
  79. }
  80. return kv;
  81. }
  82. template <class Key, class Value>
  83. inline const typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::Find(const Key &inKey, size_t inKeyHash) const
  84. {
  85. // Get the offset to the keyvalue object from the bucket with corresponding hash
  86. uint32 offset = mBuckets[inKeyHash & (mNumBuckets - 1)];
  87. while (offset != cInvalidHandle)
  88. {
  89. // Loop through linked list of values until the right one is found
  90. const KeyValue *kv = reinterpret_cast<const KeyValue *>(mObjectStore + offset);
  91. if (kv->mKey == inKey)
  92. return kv;
  93. offset = kv->mNextOffset;
  94. }
  95. // Not found
  96. return nullptr;
  97. }
  98. template <class Key, class Value>
  99. inline uint32 LockFreeHashMap<Key, Value>::ToHandle(const KeyValue *inKeyValue) const
  100. {
  101. const uint8 *kv = reinterpret_cast<const uint8 *>(inKeyValue);
  102. JPH_ASSERT(kv >= mObjectStore && kv < mObjectStore + mObjectStoreSizeBytes);
  103. return uint32(kv - mObjectStore);
  104. }
  105. template <class Key, class Value>
  106. inline const typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::FromHandle(uint32 inHandle) const
  107. {
  108. JPH_ASSERT(inHandle < mObjectStoreSizeBytes);
  109. return reinterpret_cast<const KeyValue *>(mObjectStore + inHandle);
  110. }
  111. template <class Key, class Value>
  112. inline void LockFreeHashMap<Key, Value>::GetAllKeyValues(vector<const KeyValue *> &outAll) const
  113. {
  114. for (atomic<uint32> *bucket = mBuckets; bucket < mBuckets + mNumBuckets; ++bucket)
  115. {
  116. uint32 offset = *bucket;
  117. while (offset != cInvalidHandle)
  118. {
  119. const KeyValue *kv = reinterpret_cast<const KeyValue *>(mObjectStore + offset);
  120. outAll.push_back(kv);
  121. offset = kv->mNextOffset;
  122. }
  123. }
  124. }
  125. template <class Key, class Value>
  126. typename LockFreeHashMap<Key, Value>::Iterator LockFreeHashMap<Key, Value>::begin()
  127. {
  128. // Start with the first bucket
  129. Iterator it { this, 0, mBuckets[0] };
  130. // If it doesn't contain a valid entry, use the ++ operator to find the first valid entry
  131. if (it.mOffset == cInvalidHandle)
  132. ++it;
  133. return it;
  134. }
  135. template <class Key, class Value>
  136. typename LockFreeHashMap<Key, Value>::Iterator LockFreeHashMap<Key, Value>::end()
  137. {
  138. return { this, mNumBuckets, cInvalidHandle };
  139. }
  140. template <class Key, class Value>
  141. typename LockFreeHashMap<Key, Value>::KeyValue &LockFreeHashMap<Key, Value>::Iterator::operator* ()
  142. {
  143. JPH_ASSERT(mOffset != cInvalidHandle);
  144. return *reinterpret_cast<KeyValue *>(mMap->mObjectStore + mOffset);
  145. }
  146. template <class Key, class Value>
  147. typename LockFreeHashMap<Key, Value>::Iterator &LockFreeHashMap<Key, Value>::Iterator::operator++ ()
  148. {
  149. JPH_ASSERT(mBucket < mMap->mNumBuckets);
  150. // Find the next key value in this bucket
  151. if (mOffset != cInvalidHandle)
  152. {
  153. const KeyValue *kv = reinterpret_cast<const KeyValue *>(mMap->mObjectStore + mOffset);
  154. mOffset = kv->mNextOffset;
  155. if (mOffset != cInvalidHandle)
  156. return *this;
  157. }
  158. // Loop over next buckets
  159. for (;;)
  160. {
  161. // Next bucket
  162. ++mBucket;
  163. if (mBucket >= mMap->mNumBuckets)
  164. return *this;
  165. // Fetch the first entry in the bucket
  166. mOffset = mMap->mBuckets[mBucket];
  167. if (mOffset != cInvalidHandle)
  168. return *this;
  169. }
  170. }
  171. #ifdef _DEBUG
  172. template <class Key, class Value>
  173. void LockFreeHashMap<Key, Value>::TraceStats() const
  174. {
  175. const int cMaxPerBucket = 256;
  176. int max_objects_per_bucket = 0;
  177. int num_objects = 0;
  178. int histogram[cMaxPerBucket];
  179. for (int i = 0; i < cMaxPerBucket; ++i)
  180. histogram[i] = 0;
  181. for (atomic<uint32> *bucket = mBuckets, *bucket_end = mBuckets + mNumBuckets; bucket < bucket_end; ++bucket)
  182. {
  183. int objects_in_bucket = 0;
  184. uint32 offset = *bucket;
  185. while (offset != cInvalidHandle)
  186. {
  187. const KeyValue *kv = reinterpret_cast<const KeyValue *>(mObjectStore + offset);
  188. offset = kv->mNextOffset;
  189. ++objects_in_bucket;
  190. ++num_objects;
  191. }
  192. max_objects_per_bucket = max(objects_in_bucket, max_objects_per_bucket);
  193. histogram[min(objects_in_bucket, cMaxPerBucket - 1)]++;
  194. }
  195. Trace("max_objects_per_bucket = %d, num_buckets = %d, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects);
  196. for (int i = 0; i < cMaxPerBucket; ++i)
  197. if (histogram[i] != 0)
  198. Trace("%d: %d", i, histogram[i]);
  199. }
  200. #endif
  201. } // JPH