LockFreeHashMap.inl 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  2. // SPDX-License-Identifier: MIT
  3. #pragma once
  4. JPH_NAMESPACE_BEGIN
  5. ///////////////////////////////////////////////////////////////////////////////////
  6. // LFHMAllocator
  7. ///////////////////////////////////////////////////////////////////////////////////
  8. inline LFHMAllocator::~LFHMAllocator()
  9. {
  10. AlignedFree(mObjectStore);
  11. }
  12. inline void LFHMAllocator::Init(uint inObjectStoreSizeBytes)
  13. {
  14. JPH_ASSERT(mObjectStore == nullptr);
  15. mObjectStoreSizeBytes = inObjectStoreSizeBytes;
  16. mObjectStore = reinterpret_cast<uint8 *>(JPH::AlignedAllocate(inObjectStoreSizeBytes, 16));
  17. }
  18. inline void LFHMAllocator::Clear()
  19. {
  20. mWriteOffset = 0;
  21. }
  22. inline void LFHMAllocator::Allocate(uint32 inBlockSize, uint32 &ioBegin, uint32 &ioEnd)
  23. {
  24. // If we're already beyond the end of our buffer then don't do an atomic add.
  25. // It's possible that many keys are inserted after the allocator is full, making it possible
  26. // for mWriteOffset (uint32) to wrap around to zero. When this happens, there will be a memory corruption.
  27. // This way, we will be able to progress the write offset beyond the size of the buffer
  28. // worst case by max <CPU count> * inBlockSize.
  29. if (mWriteOffset.load(memory_order_relaxed) >= mObjectStoreSizeBytes)
  30. return;
  31. // Atomically fetch a block from the pool
  32. uint32 begin = mWriteOffset.fetch_add(inBlockSize, memory_order_relaxed);
  33. uint32 end = min(begin + inBlockSize, mObjectStoreSizeBytes);
  34. if (ioEnd == begin)
  35. {
  36. // Block is allocated straight after our previous block
  37. begin = ioBegin;
  38. }
  39. else
  40. {
  41. // Block is a new block
  42. begin = min(begin, mObjectStoreSizeBytes);
  43. }
  44. // Store the begin and end of the resulting block
  45. ioBegin = begin;
  46. ioEnd = end;
  47. }
  48. template <class T>
  49. inline uint32 LFHMAllocator::ToOffset(const T *inData) const
  50. {
  51. const uint8 *data = reinterpret_cast<const uint8 *>(inData);
  52. JPH_ASSERT(data >= mObjectStore && data < mObjectStore + mObjectStoreSizeBytes);
  53. return uint32(data - mObjectStore);
  54. }
  55. template <class T>
  56. inline T *LFHMAllocator::FromOffset(uint32 inOffset) const
  57. {
  58. JPH_ASSERT(inOffset < mObjectStoreSizeBytes);
  59. return reinterpret_cast<T *>(mObjectStore + inOffset);
  60. }
  61. ///////////////////////////////////////////////////////////////////////////////////
  62. // LFHMAllocatorContext
  63. ///////////////////////////////////////////////////////////////////////////////////
  64. inline LFHMAllocatorContext::LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize) :
  65. mAllocator(inAllocator),
  66. mBlockSize(inBlockSize)
  67. {
  68. }
  69. inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset)
  70. {
  71. // Calculate needed bytes for alignment
  72. JPH_ASSERT(IsPowerOf2(inAlignment));
  73. uint32 alignment_mask = inAlignment - 1;
  74. uint32 alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
  75. // Check if we have space
  76. if (mEnd - mBegin < inSize + alignment)
  77. {
  78. // Allocate a new block
  79. mAllocator.Allocate(mBlockSize, mBegin, mEnd);
  80. // Update alignment
  81. alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
  82. // Check if we have space again
  83. if (mEnd - mBegin < inSize + alignment)
  84. return false;
  85. }
  86. // Make the allocation
  87. mBegin += alignment;
  88. outWriteOffset = mBegin;
  89. mBegin += inSize;
  90. return true;
  91. }
  92. ///////////////////////////////////////////////////////////////////////////////////
  93. // LockFreeHashMap
  94. ///////////////////////////////////////////////////////////////////////////////////
  95. template <class Key, class Value>
  96. void LockFreeHashMap<Key, Value>::Init(uint32 inMaxBuckets)
  97. {
  98. JPH_ASSERT(inMaxBuckets >= 4 && IsPowerOf2(inMaxBuckets));
  99. JPH_ASSERT(mBuckets == nullptr);
  100. mNumBuckets = inMaxBuckets;
  101. mMaxBuckets = inMaxBuckets;
  102. mBuckets = reinterpret_cast<atomic<uint32> *>(AlignedAllocate(inMaxBuckets * sizeof(atomic<uint32>), 16));
  103. Clear();
  104. }
  105. template <class Key, class Value>
  106. LockFreeHashMap<Key, Value>::~LockFreeHashMap()
  107. {
  108. AlignedFree(mBuckets);
  109. }
  110. template <class Key, class Value>
  111. void LockFreeHashMap<Key, Value>::Clear()
  112. {
  113. #ifdef JPH_ENABLE_ASSERTS
  114. // Reset number of key value pairs
  115. mNumKeyValues = 0;
  116. #endif // JPH_ENABLE_ASSERTS
  117. // Reset buckets 4 at a time
  118. static_assert(sizeof(atomic<uint32>) == sizeof(uint32));
  119. UVec4 invalid_handle = UVec4::sReplicate(cInvalidHandle);
  120. uint32 *start = reinterpret_cast<uint32 *>(mBuckets);
  121. const uint32 *end = start + mNumBuckets;
  122. JPH_ASSERT(IsAligned(start, 16));
  123. while (start < end)
  124. {
  125. invalid_handle.StoreInt4Aligned(start);
  126. start += 4;
  127. }
  128. }
  129. template <class Key, class Value>
  130. void LockFreeHashMap<Key, Value>::SetNumBuckets(uint32 inNumBuckets)
  131. {
  132. JPH_ASSERT(mNumKeyValues == 0);
  133. JPH_ASSERT(inNumBuckets <= mMaxBuckets);
  134. JPH_ASSERT(inNumBuckets >= 4 && IsPowerOf2(inNumBuckets));
  135. mNumBuckets = inNumBuckets;
  136. }
  137. template <class Key, class Value>
  138. template <class... Params>
  139. inline typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams)
  140. {
  141. // This is not a multi map, test the key hasn't been inserted yet
  142. JPH_ASSERT(Find(inKey, inKeyHash) == nullptr);
  143. // Calculate total size
  144. uint size = sizeof(KeyValue) + inExtraBytes;
  145. // Get the write offset for this key value pair
  146. uint32 write_offset;
  147. if (!ioContext.Allocate(size, alignof(KeyValue), write_offset))
  148. return nullptr;
  149. #ifdef JPH_ENABLE_ASSERTS
  150. // Increment amount of entries in map
  151. mNumKeyValues.fetch_add(1, memory_order_relaxed);
  152. #endif // JPH_ENABLE_ASSERTS
  153. // Construct the key/value pair
  154. KeyValue *kv = mAllocator.template FromOffset<KeyValue>(write_offset);
  155. JPH_ASSERT(intptr_t(kv) % alignof(KeyValue) == 0);
  156. #ifdef _DEBUG
  157. memset(kv, 0xcd, size);
  158. #endif
  159. kv->mKey = inKey;
  160. new (&kv->mValue) Value(std::forward<Params>(inConstructorParams)...);
  161. // Get the offset to the first object from the bucket with corresponding hash
  162. atomic<uint32> &offset = mBuckets[inKeyHash & (mNumBuckets - 1)];
  163. // Add this entry as the first element in the linked list
  164. uint32 old_offset = offset.load(memory_order_relaxed);
  165. for (;;)
  166. {
  167. kv->mNextOffset = old_offset;
  168. if (offset.compare_exchange_weak(old_offset, write_offset, memory_order_release))
  169. break;
  170. }
  171. return kv;
  172. }
  173. template <class Key, class Value>
  174. inline const typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::Find(const Key &inKey, uint64 inKeyHash) const
  175. {
  176. // Get the offset to the keyvalue object from the bucket with corresponding hash
  177. uint32 offset = mBuckets[inKeyHash & (mNumBuckets - 1)].load(memory_order_acquire);
  178. while (offset != cInvalidHandle)
  179. {
  180. // Loop through linked list of values until the right one is found
  181. const KeyValue *kv = mAllocator.template FromOffset<const KeyValue>(offset);
  182. if (kv->mKey == inKey)
  183. return kv;
  184. offset = kv->mNextOffset;
  185. }
  186. // Not found
  187. return nullptr;
  188. }
  189. template <class Key, class Value>
  190. inline uint32 LockFreeHashMap<Key, Value>::ToHandle(const KeyValue *inKeyValue) const
  191. {
  192. return mAllocator.ToOffset(inKeyValue);
  193. }
  194. template <class Key, class Value>
  195. inline const typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::FromHandle(uint32 inHandle) const
  196. {
  197. return mAllocator.template FromOffset<const KeyValue>(inHandle);
  198. }
  199. template <class Key, class Value>
  200. inline void LockFreeHashMap<Key, Value>::GetAllKeyValues(Array<const KeyValue *> &outAll) const
  201. {
  202. for (const atomic<uint32> *bucket = mBuckets; bucket < mBuckets + mNumBuckets; ++bucket)
  203. {
  204. uint32 offset = *bucket;
  205. while (offset != cInvalidHandle)
  206. {
  207. const KeyValue *kv = mAllocator.template FromOffset<const KeyValue>(offset);
  208. outAll.push_back(kv);
  209. offset = kv->mNextOffset;
  210. }
  211. }
  212. }
  213. template <class Key, class Value>
  214. typename LockFreeHashMap<Key, Value>::Iterator LockFreeHashMap<Key, Value>::begin()
  215. {
  216. // Start with the first bucket
  217. Iterator it { this, 0, mBuckets[0] };
  218. // If it doesn't contain a valid entry, use the ++ operator to find the first valid entry
  219. if (it.mOffset == cInvalidHandle)
  220. ++it;
  221. return it;
  222. }
  223. template <class Key, class Value>
  224. typename LockFreeHashMap<Key, Value>::Iterator LockFreeHashMap<Key, Value>::end()
  225. {
  226. return { this, mNumBuckets, cInvalidHandle };
  227. }
  228. template <class Key, class Value>
  229. typename LockFreeHashMap<Key, Value>::KeyValue &LockFreeHashMap<Key, Value>::Iterator::operator* ()
  230. {
  231. JPH_ASSERT(mOffset != cInvalidHandle);
  232. return *mMap->mAllocator.template FromOffset<KeyValue>(mOffset);
  233. }
  234. template <class Key, class Value>
  235. typename LockFreeHashMap<Key, Value>::Iterator &LockFreeHashMap<Key, Value>::Iterator::operator++ ()
  236. {
  237. JPH_ASSERT(mBucket < mMap->mNumBuckets);
  238. // Find the next key value in this bucket
  239. if (mOffset != cInvalidHandle)
  240. {
  241. const KeyValue *kv = mMap->mAllocator.template FromOffset<const KeyValue>(mOffset);
  242. mOffset = kv->mNextOffset;
  243. if (mOffset != cInvalidHandle)
  244. return *this;
  245. }
  246. // Loop over next buckets
  247. for (;;)
  248. {
  249. // Next bucket
  250. ++mBucket;
  251. if (mBucket >= mMap->mNumBuckets)
  252. return *this;
  253. // Fetch the first entry in the bucket
  254. mOffset = mMap->mBuckets[mBucket];
  255. if (mOffset != cInvalidHandle)
  256. return *this;
  257. }
  258. }
  259. #ifdef _DEBUG
  260. template <class Key, class Value>
  261. void LockFreeHashMap<Key, Value>::TraceStats() const
  262. {
  263. const int cMaxPerBucket = 256;
  264. int max_objects_per_bucket = 0;
  265. int num_objects = 0;
  266. int histogram[cMaxPerBucket];
  267. for (int i = 0; i < cMaxPerBucket; ++i)
  268. histogram[i] = 0;
  269. for (atomic<uint32> *bucket = mBuckets, *bucket_end = mBuckets + mNumBuckets; bucket < bucket_end; ++bucket)
  270. {
  271. int objects_in_bucket = 0;
  272. uint32 offset = *bucket;
  273. while (offset != cInvalidHandle)
  274. {
  275. const KeyValue *kv = mAllocator.template FromOffset<const KeyValue>(offset);
  276. offset = kv->mNextOffset;
  277. ++objects_in_bucket;
  278. ++num_objects;
  279. }
  280. max_objects_per_bucket = max(objects_in_bucket, max_objects_per_bucket);
  281. histogram[min(objects_in_bucket, cMaxPerBucket - 1)]++;
  282. }
  283. Trace("max_objects_per_bucket = %d, num_buckets = %d, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects);
  284. for (int i = 0; i < cMaxPerBucket; ++i)
  285. if (histogram[i] != 0)
  286. Trace("%d: %d", i, histogram[i]);
  287. }
  288. #endif
  289. JPH_NAMESPACE_END