LockFreeHashMap.inl 10 KB

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