Jolt Physics
A multi core friendly Game Physics Engine
Loading...
Searching...
No Matches
LockFreeHashMap.inl
Go to the documentation of this file.
1// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
8
10// LFHMAllocator
12
14{
15 AlignedFree(mObjectStore);
16}
17
18inline void LFHMAllocator::Init(uint inObjectStoreSizeBytes)
19{
20 JPH_ASSERT(mObjectStore == nullptr);
21
22 mObjectStoreSizeBytes = inObjectStoreSizeBytes;
23 mObjectStore = reinterpret_cast<uint8 *>(JPH::AlignedAllocate(inObjectStoreSizeBytes, 16));
24}
25
27{
28 mWriteOffset = 0;
29}
30
31inline void LFHMAllocator::Allocate(uint32 inBlockSize, uint32 &ioBegin, uint32 &ioEnd)
32{
33 // If we're already beyond the end of our buffer then don't do an atomic add.
34 // It's possible that many keys are inserted after the allocator is full, making it possible
35 // for mWriteOffset (uint32) to wrap around to zero. When this happens, there will be a memory corruption.
36 // This way, we will be able to progress the write offset beyond the size of the buffer
37 // worst case by max <CPU count> * inBlockSize.
38 if (mWriteOffset.load(memory_order_relaxed) >= mObjectStoreSizeBytes)
39 return;
40
41 // Atomically fetch a block from the pool
42 uint32 begin = mWriteOffset.fetch_add(inBlockSize, memory_order_relaxed);
43 uint32 end = min(begin + inBlockSize, mObjectStoreSizeBytes);
44
45 if (ioEnd == begin)
46 {
47 // Block is allocated straight after our previous block
48 begin = ioBegin;
49 }
50 else
51 {
52 // Block is a new block
53 begin = min(begin, mObjectStoreSizeBytes);
54 }
55
56 // Store the begin and end of the resulting block
57 ioBegin = begin;
58 ioEnd = end;
59}
60
61template <class T>
62inline uint32 LFHMAllocator::ToOffset(const T *inData) const
63{
64 const uint8 *data = reinterpret_cast<const uint8 *>(inData);
65 JPH_ASSERT(data >= mObjectStore && data < mObjectStore + mObjectStoreSizeBytes);
66 return uint32(data - mObjectStore);
67}
68
69template <class T>
70inline T *LFHMAllocator::FromOffset(uint32 inOffset) const
71{
72 JPH_ASSERT(inOffset < mObjectStoreSizeBytes);
73 return reinterpret_cast<T *>(mObjectStore + inOffset);
74}
75
77// LFHMAllocatorContext
79
81 mAllocator(inAllocator),
82 mBlockSize(inBlockSize)
83{
84}
85
86inline bool LFHMAllocatorContext::Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset)
87{
88 // Calculate needed bytes for alignment
89 JPH_ASSERT(IsPowerOf2(inAlignment));
90 uint32 alignment_mask = inAlignment - 1;
91 uint32 alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
92
93 // Check if we have space
94 if (mEnd - mBegin < inSize + alignment)
95 {
96 // Allocate a new block
97 mAllocator.Allocate(mBlockSize, mBegin, mEnd);
98
99 // Update alignment
100 alignment = (inAlignment - (mBegin & alignment_mask)) & alignment_mask;
101
102 // Check if we have space again
103 if (mEnd - mBegin < inSize + alignment)
104 return false;
105 }
106
107 // Make the allocation
108 mBegin += alignment;
109 outWriteOffset = mBegin;
110 mBegin += inSize;
111 return true;
112}
113
115// LockFreeHashMap
118template <class Key, class Value>
121 JPH_ASSERT(inMaxBuckets >= 4 && IsPowerOf2(inMaxBuckets));
122 JPH_ASSERT(mBuckets == nullptr);
123
124 mNumBuckets = inMaxBuckets;
125 mMaxBuckets = inMaxBuckets;
127 mBuckets = reinterpret_cast<atomic<uint32> *>(AlignedAllocate(inMaxBuckets * sizeof(atomic<uint32>), 16));
128
129 Clear();
130}
131
132template <class Key, class Value>
137
138template <class Key, class Value>
140{
141#ifdef JPH_ENABLE_ASSERTS
142 // Reset number of key value pairs
143 mNumKeyValues = 0;
144#endif // JPH_ENABLE_ASSERTS
145
146 // Reset buckets 4 at a time
147 static_assert(sizeof(atomic<uint32>) == sizeof(uint32));
148 UVec4 invalid_handle = UVec4::sReplicate(cInvalidHandle);
149 uint32 *start = reinterpret_cast<uint32 *>(mBuckets);
150 const uint32 *end = start + mNumBuckets;
152 while (start < end)
153 {
154 invalid_handle.StoreInt4Aligned(start);
155 start += 4;
156 }
157}
158
159template <class Key, class Value>
162 JPH_ASSERT(mNumKeyValues == 0);
163 JPH_ASSERT(inNumBuckets <= mMaxBuckets);
164 JPH_ASSERT(inNumBuckets >= 4 && IsPowerOf2(inNumBuckets));
165
166 mNumBuckets = inNumBuckets;
167}
168
169template <class Key, class Value>
170template <class... Params>
171inline typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams)
172{
173 // This is not a multi map, test the key hasn't been inserted yet
174 JPH_ASSERT(Find(inKey, inKeyHash) == nullptr);
175
176 // Calculate total size
177 uint size = sizeof(KeyValue) + inExtraBytes;
178
179 // Get the write offset for this key value pair
180 uint32 write_offset;
181 if (!ioContext.Allocate(size, alignof(KeyValue), write_offset))
182 return nullptr;
183
184#ifdef JPH_ENABLE_ASSERTS
185 // Increment amount of entries in map
186 mNumKeyValues.fetch_add(1, memory_order_relaxed);
187#endif // JPH_ENABLE_ASSERTS
188
189 // Construct the key/value pair
190 KeyValue *kv = mAllocator.template FromOffset<KeyValue>(write_offset);
191 JPH_ASSERT(intptr_t(kv) % alignof(KeyValue) == 0);
192#ifdef JPH_DEBUG
193 memset(kv, 0xcd, size);
194#endif
195 kv->mKey = inKey;
196 new (&kv->mValue) Value(std::forward<Params>(inConstructorParams)...);
197
198 // Get the offset to the first object from the bucket with corresponding hash
199 atomic<uint32> &offset = mBuckets[inKeyHash & (mNumBuckets - 1)];
200
201 // Add this entry as the first element in the linked list
202 uint32 old_offset = offset.load(memory_order_relaxed);
203 for (;;)
204 {
205 kv->mNextOffset = old_offset;
206 if (offset.compare_exchange_weak(old_offset, write_offset, memory_order_release))
207 break;
208 }
209
210 return kv;
211}
212
213template <class Key, class Value>
214inline const typename LockFreeHashMap<Key, Value>::KeyValue *LockFreeHashMap<Key, Value>::Find(const Key &inKey, uint64 inKeyHash) const
215{
216 // Get the offset to the keyvalue object from the bucket with corresponding hash
217 uint32 offset = mBuckets[inKeyHash & (mNumBuckets - 1)].load(memory_order_acquire);
218 while (offset != cInvalidHandle)
219 {
220 // Loop through linked list of values until the right one is found
221 const KeyValue *kv = mAllocator.template FromOffset<const KeyValue>(offset);
222 if (kv->mKey == inKey)
223 return kv;
224 offset = kv->mNextOffset;
225 }
226
227 // Not found
228 return nullptr;
229}
230
231template <class Key, class Value>
233{
234 return mAllocator.ToOffset(inKeyValue);
235}
236
237template <class Key, class Value>
239{
240 return mAllocator.template FromOffset<const KeyValue>(inHandle);
241}
242
243template <class Key, class Value>
245{
246 for (const atomic<uint32> *bucket = mBuckets; bucket < mBuckets + mNumBuckets; ++bucket)
247 {
248 uint32 offset = *bucket;
249 while (offset != cInvalidHandle)
250 {
251 const KeyValue *kv = mAllocator.template FromOffset<const KeyValue>(offset);
252 outAll.push_back(kv);
253 offset = kv->mNextOffset;
254 }
255 }
256}
257
258template <class Key, class Value>
260{
261 // Start with the first bucket
262 Iterator it { this, 0, mBuckets[0] };
263
264 // If it doesn't contain a valid entry, use the ++ operator to find the first valid entry
265 if (it.mOffset == cInvalidHandle)
266 ++it;
267
268 return it;
269}
270
271template <class Key, class Value>
273{
274 return { this, mNumBuckets, cInvalidHandle };
275}
276
277template <class Key, class Value>
279{
281
282 return *mMap->mAllocator.template FromOffset<KeyValue>(mOffset);
283}
284
285template <class Key, class Value>
287{
288 JPH_ASSERT(mBucket < mMap->mNumBuckets);
289
290 // Find the next key value in this bucket
291 if (mOffset != cInvalidHandle)
292 {
293 const KeyValue *kv = mMap->mAllocator.template FromOffset<const KeyValue>(mOffset);
294 mOffset = kv->mNextOffset;
295 if (mOffset != cInvalidHandle)
296 return *this;
297 }
298
299 // Loop over next buckets
300 for (;;)
301 {
302 // Next bucket
303 ++mBucket;
304 if (mBucket >= mMap->mNumBuckets)
305 return *this;
306
307 // Fetch the first entry in the bucket
308 mOffset = mMap->mBuckets[mBucket];
309 if (mOffset != cInvalidHandle)
310 return *this;
311 }
312}
313
314#ifdef JPH_DEBUG
315
316template <class Key, class Value>
318{
319 const int cMaxPerBucket = 256;
320
321 int max_objects_per_bucket = 0;
322 int num_objects = 0;
323 int histogram[cMaxPerBucket];
324 for (int i = 0; i < cMaxPerBucket; ++i)
325 histogram[i] = 0;
326
327 for (atomic<uint32> *bucket = mBuckets, *bucket_end = mBuckets + mNumBuckets; bucket < bucket_end; ++bucket)
328 {
329 int objects_in_bucket = 0;
330 uint32 offset = *bucket;
331 while (offset != cInvalidHandle)
332 {
333 const KeyValue *kv = mAllocator.template FromOffset<const KeyValue>(offset);
334 offset = kv->mNextOffset;
335 ++objects_in_bucket;
336 ++num_objects;
337 }
338 max_objects_per_bucket = max(objects_in_bucket, max_objects_per_bucket);
339 histogram[min(objects_in_bucket, cMaxPerBucket - 1)]++;
340 }
341
342 Trace("max_objects_per_bucket = %d, num_buckets = %u, num_objects = %d", max_objects_per_bucket, mNumBuckets, num_objects);
343
344 for (int i = 0; i < cMaxPerBucket; ++i)
345 if (histogram[i] != 0)
346 Trace("%d: %d", i, histogram[i]);
347}
348
349#endif
350
std::uint8_t uint8
Definition Core.h:454
std::uint64_t uint64
Definition Core.h:457
unsigned int uint
Definition Core.h:453
#define JPH_NAMESPACE_END
Definition Core.h:379
std::uint32_t uint32
Definition Core.h:456
#define JPH_NAMESPACE_BEGIN
Definition Core.h:373
TraceFunction Trace
Definition IssueReporting.cpp:18
#define JPH_ASSERT(...)
Definition IssueReporting.h:33
constexpr bool IsPowerOf2(T inV)
Check if inV is a power of 2.
Definition Math.h:73
bool IsAligned(T inV, uint64 inAlignment)
Check if inV is inAlignment aligned.
Definition Math.h:88
AlignedFreeFunction AlignedFree
Definition Memory.cpp:72
AlignedAllocateFunction AlignedAllocate
Definition Memory.cpp:71
Definition Array.h:36
void push_back(const T &inValue)
Add element to the back of the array.
Definition Array.h:277
Definition LockFreeHashMap.h:49
LFHMAllocatorContext(LFHMAllocator &inAllocator, uint32 inBlockSize)
Construct a new allocator context.
Definition LockFreeHashMap.inl:80
bool Allocate(uint32 inSize, uint32 inAlignment, uint32 &outWriteOffset)
Allocate data block.
Definition LockFreeHashMap.inl:86
Allocator for a lock free hash map.
Definition LockFreeHashMap.h:14
uint32 ToOffset(const T *inData) const
Convert a pointer to an offset.
Definition LockFreeHashMap.inl:62
~LFHMAllocator()
Destructor.
Definition LockFreeHashMap.inl:13
void Init(uint inObjectStoreSizeBytes)
Definition LockFreeHashMap.inl:18
T * FromOffset(uint32 inOffset) const
Convert an offset to a pointer.
Definition LockFreeHashMap.inl:70
void Allocate(uint32 inBlockSize, uint32 &ioBegin, uint32 &ioEnd)
Definition LockFreeHashMap.inl:31
void Clear()
Clear all allocations.
Definition LockFreeHashMap.inl:26
A key / value pair that is inserted in the map.
Definition LockFreeHashMap.h:100
Definition LockFreeHashMap.h:72
const KeyValue * FromHandle(uint32 inHandle) const
Convert uint32 handle back to key and value.
Definition LockFreeHashMap.inl:238
Iterator begin()
Definition LockFreeHashMap.inl:259
void GetAllKeyValues(Array< const KeyValue * > &outAll) const
Get all key/value pairs.
Definition LockFreeHashMap.inl:244
void Init(uint32 inMaxBuckets)
Definition LockFreeHashMap.inl:119
void SetNumBuckets(uint32 inNumBuckets)
Definition LockFreeHashMap.inl:160
KeyValue * Create(LFHMAllocatorContext &ioContext, const Key &inKey, uint64 inKeyHash, int inExtraBytes, Params &&... inConstructorParams)
Definition LockFreeHashMap.inl:171
~LockFreeHashMap()
Definition LockFreeHashMap.inl:133
const KeyValue * Find(const Key &inKey, uint64 inKeyHash) const
Find an element, returns null if not found.
Definition LockFreeHashMap.inl:214
uint32 ToHandle(const KeyValue *inKeyValue) const
Get convert key value pair to uint32 handle.
Definition LockFreeHashMap.inl:232
Iterator end()
Definition LockFreeHashMap.inl:272
static const uint32 cInvalidHandle
Value of an invalid handle.
Definition LockFreeHashMap.h:123
void Clear()
Definition LockFreeHashMap.inl:139
Definition UVec4.h:12
static JPH_INLINE UVec4 sReplicate(uint32 inV)
Replicate int inV across all components.
Definition UVec4.inl:56
JPH_INLINE void StoreInt4Aligned(uint32 *outV) const
Store 4 ints to memory, aligned to 16 bytes.
Definition UVec4.inl:363
Non-const iterator.
Definition LockFreeHashMap.h:142
uint32 mOffset
Definition LockFreeHashMap.h:155
Iterator & operator++()
Next item.
Definition LockFreeHashMap.inl:286
MapType * mMap
Definition LockFreeHashMap.h:153
KeyValue & operator*()
Convert to key value pair.
Definition LockFreeHashMap.inl:278