LockFreeHashMap.h 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2021 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #pragma once
  5. #include <Jolt/Core/NonCopyable.h>
  6. #include <Jolt/Core/Atomics.h>
  7. JPH_NAMESPACE_BEGIN
  8. /// Allocator for a lock free hash map
  9. class LFHMAllocator : public NonCopyable
  10. {
  11. public:
  12. /// Destructor
  13. inline ~LFHMAllocator();
  14. /// Initialize the allocator
  15. /// @param inObjectStoreSizeBytes Number of bytes to reserve for all key value pairs
  16. inline void Init(uint inObjectStoreSizeBytes);
  17. /// Clear all allocations
  18. inline void Clear();
  19. /// Allocate a new block of data
  20. /// @param inBlockSize Size of block to allocate (will potentially return a smaller block if memory is full).
  21. /// @param ioBegin Should be the start of the first free byte in current memory block on input, will contain the start of the first free byte in allocated block on return.
  22. /// @param ioEnd Should be the byte beyond the current memory block on input, will contain the byte beyond the allocated block on return.
  23. inline void Allocate(uint32 inBlockSize, uint32 &ioBegin, uint32 &ioEnd);
  24. /// Convert a pointer to an offset
  25. template <class T>
  26. inline uint32 ToOffset(const T *inData) const;
  27. /// Convert an offset to a pointer
  28. template <class T>
  29. inline T * FromOffset(uint32 inOffset) const;
  30. private:
  31. uint8 * mObjectStore = nullptr; ///< This contains a contigous list of objects (possibly of varying size)
  32. uint32 mObjectStoreSizeBytes = 0; ///< The size of mObjectStore in bytes
  33. atomic<uint32> mWriteOffset { 0 }; ///< Next offset to write to in mObjectStore
  34. };
  35. /// Allocator context object for a lock free hash map that allocates a larger memory block at once and hands it out in smaller portions.
  36. /// This avoids contention on the atomic LFHMAllocator::mWriteOffset.
  37. class LFHMAllocatorContext : public NonCopyable
  38. {
  39. public:
  40. /// Construct a new allocator context
  41. inline LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize);
  42. /// @brief Allocate data block
  43. /// @param inSize Size of block to allocate.
  44. /// @param inAlignment Alignment of block to allocate.
  45. /// @param outWriteOffset Offset in buffer where block is located
  46. /// @return True if allocation succeeded
  47. inline bool Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset);
  48. private:
  49. LFHMAllocator & mAllocator;
  50. uint32 mBlockSize;
  51. uint32 mBegin = 0;
  52. uint32 mEnd = 0;
  53. };
  54. /// Very simple lock free hash map that only allows insertion, retrieval and provides a fixed amount of buckets and fixed storage.
  55. /// Note: This class currently assumes key and value are simple types that need no calls to the destructor.
  56. template <class Key, class Value>
  57. class LockFreeHashMap : public NonCopyable
  58. {
  59. public:
  60. using MapType = LockFreeHashMap<Key, Value>;
  61. /// Destructor
  62. explicit LockFreeHashMap(LFHMAllocator &inAllocator) : mAllocator(inAllocator) { }
  63. ~LockFreeHashMap();
  64. /// Initialization
  65. /// @param inMaxBuckets Max amount of buckets to use in the hashmap. Must be power of 2.
  66. void Init(uint32 inMaxBuckets);
  67. /// Remove all elements.
  68. /// Note that this cannot happen simultaneously with adding new elements.
  69. void Clear();
  70. /// Get the current amount of buckets that the map is using
  71. uint32 GetNumBuckets() const { return mNumBuckets; }
  72. /// Get the maximum amount of buckets that this map supports
  73. uint32 GetMaxBuckets() const { return mMaxBuckets; }
  74. /// Update the number of buckets. This must be done after clearing the map and cannot be done concurrently with any other operations on the map.
  75. /// Note that the number of buckets can never become bigger than the specified max buckets during initialization and that it must be a power of 2.
  76. void SetNumBuckets(uint32 inNumBuckets);
  77. /// A key / value pair that is inserted in the map
  78. class KeyValue
  79. {
  80. public:
  81. const Key & GetKey() const { return mKey; }
  82. Value & GetValue() { return mValue; }
  83. const Value & GetValue() const { return mValue; }
  84. private:
  85. template <class K, class V> friend class LockFreeHashMap;
  86. Key mKey; ///< Key for this entry
  87. uint32 mNextOffset; ///< Offset in mObjectStore of next KeyValue entry with same hash
  88. Value mValue; ///< Value for this entry + optionally extra bytes
  89. };
  90. /// Insert a new element, returns null if map full.
  91. /// Multiple threads can be inserting in the map at the same time.
  92. template <class... Params>
  93. inline KeyValue * Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams);
  94. /// Find an element, returns null if not found
  95. inline const KeyValue * Find(const Key &inKey, uint64 inKeyHash) const;
  96. /// Value of an invalid handle
  97. const static uint32 cInvalidHandle = uint32(-1);
  98. /// Get convert key value pair to uint32 handle
  99. inline uint32 ToHandle(const KeyValue *inKeyValue) const;
  100. /// Convert uint32 handle back to key and value
  101. inline const KeyValue * FromHandle(uint32 inHandle) const;
  102. #ifdef JPH_ENABLE_ASSERTS
  103. /// Get the number of key value pairs that this map currently contains.
  104. /// Available only when asserts are enabled because adding elements creates contention on this atomic and negatively affects performance.
  105. inline uint32 GetNumKeyValues() const { return mNumKeyValues; }
  106. #endif // JPH_ENABLE_ASSERTS
  107. /// Get all key/value pairs
  108. inline void GetAllKeyValues(Array<const KeyValue *> &outAll) const;
  109. /// Non-const iterator
  110. struct Iterator
  111. {
  112. /// Comparison
  113. bool operator == (const Iterator &inRHS) const { return mMap == inRHS.mMap && mBucket == inRHS.mBucket && mOffset == inRHS.mOffset; }
  114. bool operator != (const Iterator &inRHS) const { return !(*this == inRHS); }
  115. /// Convert to key value pair
  116. KeyValue & operator * ();
  117. /// Next item
  118. Iterator & operator ++ ();
  119. MapType * mMap;
  120. uint32 mBucket;
  121. uint32 mOffset;
  122. };
  123. /// Iterate over the map, note that it is not safe to do this in parallel to Clear().
  124. /// It is safe to do this while adding elements to the map, but newly added elements may or may not be returned by the iterator.
  125. Iterator begin();
  126. Iterator end();
  127. #ifdef _DEBUG
  128. /// Output stats about this map to the log
  129. void TraceStats() const;
  130. #endif
  131. private:
  132. LFHMAllocator & mAllocator; ///< Allocator used to allocate key value pairs
  133. #ifdef JPH_ENABLE_ASSERTS
  134. atomic<uint32> mNumKeyValues = 0; ///< Number of key value pairs in the store
  135. #endif // JPH_ENABLE_ASSERTS
  136. atomic<uint32> * mBuckets = nullptr; ///< This contains the offset in mObjectStore of the first object with a particular hash
  137. uint32 mNumBuckets = 0; ///< Current number of buckets
  138. uint32 mMaxBuckets = 0; ///< Maximum number of buckets
  139. };
  140. JPH_NAMESPACE_END
  141. #include "LockFreeHashMap.inl"