|
@@ -112,13 +112,41 @@ private:
|
|
|
size_type mIndex;
|
|
|
};
|
|
|
|
|
|
+ /// Get the maximum number of elements that we can support given a number of buckets
|
|
|
+ static constexpr size_type sGetMaxLoad(size_type inBucketCount)
|
|
|
+ {
|
|
|
+ return uint32((cMaxLoadFactorNumerator * inBucketCount) / cMaxLoadFactorDenominator);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Update the control value for a bucket
|
|
|
+ JPH_INLINE void SetControlValue(size_type inIndex, uint8 inValue)
|
|
|
+ {
|
|
|
+ JPH_ASSERT(inIndex < mMaxSize);
|
|
|
+ mControl[inIndex] = inValue;
|
|
|
+
|
|
|
+ // Mirror the first 15 bytes at the end of the control values
|
|
|
+ if (inIndex < 15)
|
|
|
+ mControl[mMaxSize + inIndex] = inValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Get the index and control value for a particular key
|
|
|
+ JPH_INLINE void GetIndexAndControlValue(const Key &inKey, size_type &outIndex, uint8 &outControl) const
|
|
|
+ {
|
|
|
+ // Calculate hash
|
|
|
+ uint64 hash_value = Hash { } (inKey);
|
|
|
+
|
|
|
+ // Split hash into index and control value
|
|
|
+ outIndex = size_type(hash_value >> 7) & (mMaxSize - 1);
|
|
|
+ outControl = cBucketUsed | uint8(hash_value);
|
|
|
+ }
|
|
|
+
|
|
|
/// Allocate space for the hash table
|
|
|
void AllocateTable(size_type inMaxSize)
|
|
|
{
|
|
|
JPH_ASSERT(mData == nullptr);
|
|
|
|
|
|
mMaxSize = inMaxSize;
|
|
|
- mMaxLoad = uint32((cMaxLoadFactorNumerator * inMaxSize) / cMaxLoadFactorDenominator);
|
|
|
+ mLoadLeft = sGetMaxLoad(inMaxSize);
|
|
|
size_t required_size = size_t(mMaxSize) * (sizeof(KeyValue) + 1) + 15; // Add 15 bytes to mirror the first 15 bytes of the control values
|
|
|
if constexpr (cNeedsAlignedAllocate)
|
|
|
mData = reinterpret_cast<KeyValue *>(AlignedAllocate(required_size, alignof(KeyValue)));
|
|
@@ -165,7 +193,7 @@ private:
|
|
|
mControl = nullptr;
|
|
|
mSize = 0;
|
|
|
mMaxSize = 0;
|
|
|
- mMaxLoad = 0;
|
|
|
+ mLoadLeft = 0;
|
|
|
|
|
|
// Allocate new table
|
|
|
AllocateTable(new_max_size);
|
|
@@ -181,7 +209,7 @@ private:
|
|
|
{
|
|
|
size_type index;
|
|
|
KeyValue *element = old_data + i;
|
|
|
- JPH_IF_ENABLE_ASSERTS(bool inserted =) InsertKey</* AllowDeleted= */ false>(HashTableDetail::sGetKey(*element), index);
|
|
|
+ JPH_IF_ENABLE_ASSERTS(bool inserted =) InsertKey</* InsertAfterGrow= */ true>(HashTableDetail::sGetKey(*element), index);
|
|
|
JPH_ASSERT(inserted);
|
|
|
::new (mData + index) KeyValue(std::move(*element));
|
|
|
element->~KeyValue();
|
|
@@ -204,24 +232,28 @@ protected:
|
|
|
|
|
|
/// Insert a key into the map, returns true if the element was inserted, false if it already existed.
|
|
|
/// outIndex is the index at which the element should be constructed / where it is located.
|
|
|
- template <bool AllowDeleted = true>
|
|
|
+ template <bool InsertAfterGrow = false>
|
|
|
bool InsertKey(const Key &inKey, size_type &outIndex)
|
|
|
{
|
|
|
// Ensure we have enough space
|
|
|
- if (mSize + 1 >= mMaxLoad)
|
|
|
- GrowTable();
|
|
|
-
|
|
|
- // Calculate hash
|
|
|
- uint64 hash_value = Hash { } (inKey);
|
|
|
-
|
|
|
- // Split hash into control byte and index
|
|
|
- uint8 control = cBucketUsed | uint8(hash_value);
|
|
|
- size_type bucket_mask = mMaxSize - 1;
|
|
|
- size_type index = size_type(hash_value >> 7) & bucket_mask;
|
|
|
+ if (mLoadLeft == 0)
|
|
|
+ {
|
|
|
+ // Should not be growing if we're already growing!
|
|
|
+ if constexpr (InsertAfterGrow)
|
|
|
+ JPH_ASSERT(false);
|
|
|
+
|
|
|
+ // Decide if we need to clean up all tombstones or if we need to grow the map
|
|
|
+ size_type num_deleted = sGetMaxLoad(mMaxSize) - mSize;
|
|
|
+ if (num_deleted * cMaxDeletedElementsDenominator > mMaxSize * cMaxDeletedElementsNumerator)
|
|
|
+ rehash(0);
|
|
|
+ else
|
|
|
+ GrowTable();
|
|
|
+ }
|
|
|
|
|
|
- BVec16 control16 = BVec16::sReplicate(control);
|
|
|
- BVec16 bucket_empty = BVec16::sZero();
|
|
|
- BVec16 bucket_deleted = BVec16::sReplicate(cBucketDeleted);
|
|
|
+ // Split hash into index and control value
|
|
|
+ size_type index;
|
|
|
+ uint8 control;
|
|
|
+ GetIndexAndControlValue(inKey, index, control);
|
|
|
|
|
|
// Keeps track of the index of the first deleted bucket we found
|
|
|
constexpr size_type cNoDeleted = ~size_type(0);
|
|
@@ -229,40 +261,33 @@ protected:
|
|
|
|
|
|
// Linear probing
|
|
|
KeyEqual equal;
|
|
|
+ size_type bucket_mask = mMaxSize - 1;
|
|
|
+ BVec16 control16 = BVec16::sReplicate(control);
|
|
|
+ BVec16 bucket_empty = BVec16::sZero();
|
|
|
+ BVec16 bucket_deleted = BVec16::sReplicate(cBucketDeleted);
|
|
|
for (;;)
|
|
|
{
|
|
|
// Read 16 control values (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
|
|
BVec16 control_bytes = BVec16::sLoadByte16(mControl + index);
|
|
|
|
|
|
- // Check for the control value we're looking for
|
|
|
- uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());
|
|
|
+ // Check if we must find the element before we can insert
|
|
|
+ if constexpr (!InsertAfterGrow)
|
|
|
+ {
|
|
|
+ // Check for the control value we're looking for
|
|
|
+ // Note that when deleting we can create empty buckets instead of deleted buckets.
|
|
|
+ // This means we must unconditionally check all buckets in this batch for equality
|
|
|
+ // (also beyond the first empty bucket).
|
|
|
+ uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());
|
|
|
|
|
|
- // Check for empty buckets
|
|
|
- uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());
|
|
|
+ // Index within the 16 buckets
|
|
|
+ size_type local_index = index;
|
|
|
|
|
|
- // Check if we're still scanning for deleted buckets
|
|
|
- if constexpr (AllowDeleted)
|
|
|
- if (first_deleted_index == cNoDeleted)
|
|
|
+ // Loop while there's still buckets to process
|
|
|
+ while (control_equal != 0)
|
|
|
{
|
|
|
- // Check if any buckets have been deleted, if so store the first one
|
|
|
- uint32 control_deleted = uint32(BVec16::sEquals(control_bytes, bucket_deleted).GetTrues());
|
|
|
- if (control_deleted != 0)
|
|
|
- first_deleted_index = index + CountTrailingZeros(control_deleted);
|
|
|
- }
|
|
|
-
|
|
|
- // Index within the 16 buckets
|
|
|
- size_type local_index = index;
|
|
|
+ // Get the first equal bucket
|
|
|
+ uint first_equal = CountTrailingZeros(control_equal);
|
|
|
|
|
|
- // Loop while there's still buckets to process
|
|
|
- while ((control_equal | control_empty) != 0)
|
|
|
- {
|
|
|
- // Get the index of the first bucket that is either equal or empty
|
|
|
- uint first_equal = CountTrailingZeros(control_equal);
|
|
|
- uint first_empty = CountTrailingZeros(control_empty);
|
|
|
-
|
|
|
- // Check if we first found a bucket with equal control value before an empty bucket
|
|
|
- if (first_equal < first_empty)
|
|
|
- {
|
|
|
// Skip to the bucket
|
|
|
local_index += first_equal;
|
|
|
|
|
@@ -278,35 +303,47 @@ protected:
|
|
|
}
|
|
|
|
|
|
// Skip past this bucket
|
|
|
+ control_equal >>= first_equal + 1;
|
|
|
local_index++;
|
|
|
- uint shift = first_equal + 1;
|
|
|
- control_equal >>= shift;
|
|
|
- control_empty >>= shift;
|
|
|
}
|
|
|
- else
|
|
|
+
|
|
|
+ // Check if we're still scanning for deleted buckets
|
|
|
+ if (first_deleted_index == cNoDeleted)
|
|
|
{
|
|
|
- // An empty bucket was found, we can insert a new item
|
|
|
- JPH_ASSERT(control_empty != 0);
|
|
|
+ // Check if any buckets have been deleted, if so store the first one
|
|
|
+ uint32 control_deleted = uint32(BVec16::sEquals(control_bytes, bucket_deleted).GetTrues());
|
|
|
+ if (control_deleted != 0)
|
|
|
+ first_deleted_index = index + CountTrailingZeros(control_deleted);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // Get the location of the first empty or deleted bucket
|
|
|
- local_index += first_empty;
|
|
|
- if constexpr (AllowDeleted)
|
|
|
- if (first_deleted_index < local_index)
|
|
|
- local_index = first_deleted_index;
|
|
|
+ // Check for empty buckets
|
|
|
+ uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());
|
|
|
+ if (control_empty != 0)
|
|
|
+ {
|
|
|
+ // If we found a deleted bucket, use it.
|
|
|
+ // It doesn't matter if it is before or after the first empty bucket we found
|
|
|
+ // since we will always be scanning in batches of 16 buckets.
|
|
|
+ if (first_deleted_index == cNoDeleted || InsertAfterGrow)
|
|
|
+ {
|
|
|
+ index += CountTrailingZeros(control_empty);
|
|
|
+ --mLoadLeft; // Using an empty bucket decreases the load left
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ index = first_deleted_index;
|
|
|
+ }
|
|
|
|
|
|
- // Make sure that our index is not beyond the end of the table
|
|
|
- local_index &= bucket_mask;
|
|
|
+ // Make sure that our index is not beyond the end of the table
|
|
|
+ index &= bucket_mask;
|
|
|
|
|
|
- // Update control byte
|
|
|
- mControl[local_index] = control;
|
|
|
- if (local_index < 15)
|
|
|
- mControl[mMaxSize + local_index] = control; // Mirror the first 15 bytes at the end of the control values
|
|
|
- ++mSize;
|
|
|
+ // Update control byte
|
|
|
+ SetControlValue(index, control);
|
|
|
+ ++mSize;
|
|
|
|
|
|
- // Return index to newly allocated bucket
|
|
|
- outIndex = local_index;
|
|
|
- return true;
|
|
|
- }
|
|
|
+ // Return index to newly allocated bucket
|
|
|
+ outIndex = index;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
// Move to next batch of 16 buckets
|
|
@@ -388,13 +425,13 @@ public:
|
|
|
mControl(ioRHS.mControl),
|
|
|
mSize(ioRHS.mSize),
|
|
|
mMaxSize(ioRHS.mMaxSize),
|
|
|
- mMaxLoad(ioRHS.mMaxLoad)
|
|
|
+ mLoadLeft(ioRHS.mLoadLeft)
|
|
|
{
|
|
|
ioRHS.mData = nullptr;
|
|
|
ioRHS.mControl = nullptr;
|
|
|
ioRHS.mSize = 0;
|
|
|
ioRHS.mMaxSize = 0;
|
|
|
- ioRHS.mMaxLoad = 0;
|
|
|
+ ioRHS.mLoadLeft = 0;
|
|
|
}
|
|
|
|
|
|
/// Assignment operator
|
|
@@ -410,6 +447,29 @@ public:
|
|
|
return *this;
|
|
|
}
|
|
|
|
|
|
+ /// Move assignment operator
|
|
|
+ HashTable & operator = (HashTable &&ioRHS) noexcept
|
|
|
+ {
|
|
|
+ if (this != &ioRHS)
|
|
|
+ {
|
|
|
+ clear();
|
|
|
+
|
|
|
+ mData = ioRHS.mData;
|
|
|
+ mControl = ioRHS.mControl;
|
|
|
+ mSize = ioRHS.mSize;
|
|
|
+ mMaxSize = ioRHS.mMaxSize;
|
|
|
+ mLoadLeft = ioRHS.mLoadLeft;
|
|
|
+
|
|
|
+ ioRHS.mData = nullptr;
|
|
|
+ ioRHS.mControl = nullptr;
|
|
|
+ ioRHS.mSize = 0;
|
|
|
+ ioRHS.mMaxSize = 0;
|
|
|
+ ioRHS.mLoadLeft = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ return *this;
|
|
|
+ }
|
|
|
+
|
|
|
/// Destructor
|
|
|
~HashTable()
|
|
|
{
|
|
@@ -454,7 +514,7 @@ public:
|
|
|
mControl = nullptr;
|
|
|
mSize = 0;
|
|
|
mMaxSize = 0;
|
|
|
- mMaxLoad = 0;
|
|
|
+ mLoadLeft = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -494,6 +554,18 @@ public:
|
|
|
return const_iterator(this, mMaxSize);
|
|
|
}
|
|
|
|
|
|
+ /// Number of buckets in the table
|
|
|
+ size_type bucket_count() const
|
|
|
+ {
|
|
|
+ return mMaxSize;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Max number of buckets that the table can have
|
|
|
+ constexpr size_type max_bucket_count() const
|
|
|
+ {
|
|
|
+ return size_type(1) << (sizeof(size_type) * 8 - 1);
|
|
|
+ }
|
|
|
+
|
|
|
/// Check if there are no elements in the table
|
|
|
bool empty() const
|
|
|
{
|
|
@@ -506,6 +578,18 @@ public:
|
|
|
return mSize;
|
|
|
}
|
|
|
|
|
|
+ /// Max number of elements that the table can hold
|
|
|
+ constexpr size_type max_size() const
|
|
|
+ {
|
|
|
+ return size_type((uint64(max_bucket_count()) * cMaxLoadFactorNumerator) / cMaxLoadFactorDenominator);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// Get the max load factor for this table (max number of elements / number of buckets)
|
|
|
+ constexpr float max_load_factor() const
|
|
|
+ {
|
|
|
+ return float(cMaxLoadFactorNumerator) / float(cMaxLoadFactorDenominator);
|
|
|
+ }
|
|
|
+
|
|
|
/// Insert a new element, returns iterator and if the element was inserted
|
|
|
std::pair<iterator, bool> insert(const value_type &inValue)
|
|
|
{
|
|
@@ -523,68 +607,61 @@ public:
|
|
|
if (empty())
|
|
|
return cend();
|
|
|
|
|
|
- // Calculate hash
|
|
|
- uint64 hash_value = Hash { } (inKey);
|
|
|
+ // Split hash into index and control value
|
|
|
+ size_type index;
|
|
|
+ uint8 control;
|
|
|
+ GetIndexAndControlValue(inKey, index, control);
|
|
|
|
|
|
- // Split hash into control byte and index
|
|
|
- uint8 control = cBucketUsed | uint8(hash_value);
|
|
|
+ // Linear probing
|
|
|
+ KeyEqual equal;
|
|
|
size_type bucket_mask = mMaxSize - 1;
|
|
|
- size_type index = size_type(hash_value >> 7) & bucket_mask;
|
|
|
-
|
|
|
BVec16 control16 = BVec16::sReplicate(control);
|
|
|
BVec16 bucket_empty = BVec16::sZero();
|
|
|
-
|
|
|
- // Linear probing
|
|
|
- KeyEqual equal;
|
|
|
for (;;)
|
|
|
{
|
|
|
- // Read 16 control values (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
|
|
+ // Read 16 control values
|
|
|
+ // (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
|
|
BVec16 control_bytes = BVec16::sLoadByte16(mControl + index);
|
|
|
|
|
|
// Check for the control value we're looking for
|
|
|
+ // Note that when deleting we can create empty buckets instead of deleted buckets.
|
|
|
+ // This means we must unconditionally check all buckets in this batch for equality
|
|
|
+ // (also beyond the first empty bucket).
|
|
|
uint32 control_equal = uint32(BVec16::sEquals(control_bytes, control16).GetTrues());
|
|
|
|
|
|
- // Check for empty buckets
|
|
|
- uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());
|
|
|
-
|
|
|
// Index within the 16 buckets
|
|
|
size_type local_index = index;
|
|
|
|
|
|
// Loop while there's still buckets to process
|
|
|
- while ((control_equal | control_empty) != 0)
|
|
|
+ while (control_equal != 0)
|
|
|
{
|
|
|
- // Get the index of the first bucket that is either equal or empty
|
|
|
+ // Get the first equal bucket
|
|
|
uint first_equal = CountTrailingZeros(control_equal);
|
|
|
- uint first_empty = CountTrailingZeros(control_empty);
|
|
|
-
|
|
|
- // Check if we first found a bucket with equal control value before an empty bucket
|
|
|
- if (first_equal < first_empty)
|
|
|
- {
|
|
|
- // Skip to the bucket
|
|
|
- local_index += first_equal;
|
|
|
|
|
|
- // Make sure that our index is not beyond the end of the table
|
|
|
- local_index &= bucket_mask;
|
|
|
+ // Skip to the bucket
|
|
|
+ local_index += first_equal;
|
|
|
|
|
|
- // We found a bucket with same control value
|
|
|
- if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey))
|
|
|
- {
|
|
|
- // Element found
|
|
|
- return const_iterator(this, local_index);
|
|
|
- }
|
|
|
+ // Make sure that our index is not beyond the end of the table
|
|
|
+ local_index &= bucket_mask;
|
|
|
|
|
|
- // Skip past this bucket
|
|
|
- local_index++;
|
|
|
- uint shift = first_equal + 1;
|
|
|
- control_equal >>= shift;
|
|
|
- control_empty >>= shift;
|
|
|
- }
|
|
|
- else
|
|
|
+ // We found a bucket with same control value
|
|
|
+ if (equal(HashTableDetail::sGetKey(mData[local_index]), inKey))
|
|
|
{
|
|
|
- // An empty bucket was found, we didn't find the element
|
|
|
- JPH_ASSERT(control_empty != 0);
|
|
|
- return cend();
|
|
|
+ // Element found
|
|
|
+ return const_iterator(this, local_index);
|
|
|
}
|
|
|
+
|
|
|
+ // Skip past this bucket
|
|
|
+ control_equal >>= first_equal + 1;
|
|
|
+ local_index++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check for empty buckets
|
|
|
+ uint32 control_empty = uint32(BVec16::sEquals(control_bytes, bucket_empty).GetTrues());
|
|
|
+ if (control_empty != 0)
|
|
|
+ {
|
|
|
+ // An empty bucket was found, we didn't find the element
|
|
|
+ return cend();
|
|
|
}
|
|
|
|
|
|
// Move to next batch of 16 buckets
|
|
@@ -597,14 +674,30 @@ public:
|
|
|
{
|
|
|
JPH_ASSERT(inIterator.IsValid());
|
|
|
|
|
|
- // Mark the bucket as deleted
|
|
|
- mControl[inIterator.mIndex] = cBucketDeleted;
|
|
|
- if (inIterator.mIndex < 15)
|
|
|
- mControl[inIterator.mIndex + mMaxSize] = cBucketDeleted;
|
|
|
+ // Read 16 control values before and after the current index
|
|
|
+ // (note that we added 15 bytes at the end of the control values that mirror the first 15 bytes)
|
|
|
+ BVec16 control_bytes_before = BVec16::sLoadByte16(mControl + ((inIterator.mIndex - 16) & (mMaxSize - 1)));
|
|
|
+ BVec16 control_bytes_after = BVec16::sLoadByte16(mControl + inIterator.mIndex);
|
|
|
+ BVec16 bucket_empty = BVec16::sZero();
|
|
|
+ uint32 control_empty_before = uint32(BVec16::sEquals(control_bytes_before, bucket_empty).GetTrues());
|
|
|
+ uint32 control_empty_after = uint32(BVec16::sEquals(control_bytes_after, bucket_empty).GetTrues());
|
|
|
+
|
|
|
+ // If (this index including) there exist 16 consecutive non-empty slots (represented by a bit being 0) then
|
|
|
+ // a probe looking for some element needs to continue probing so we cannot mark the bucket as empty
|
|
|
+ // but must mark it as deleted instead.
|
|
|
+ // Note that we use: CountLeadingZeros(uint16) = CountLeadingZeros(uint32) - 16.
|
|
|
+ uint8 control_value = CountLeadingZeros(control_empty_before) - 16 + CountTrailingZeros(control_empty_after) < 16? cBucketEmpty : cBucketDeleted;
|
|
|
+
|
|
|
+ // Mark the bucket as empty/deleted
|
|
|
+ SetControlValue(inIterator.mIndex, control_value);
|
|
|
|
|
|
// Destruct the element
|
|
|
mData[inIterator.mIndex].~KeyValue();
|
|
|
|
|
|
+ // If we marked the bucket as empty we can increase the load left
|
|
|
+ if (control_value == cBucketEmpty)
|
|
|
+ ++mLoadLeft;
|
|
|
+
|
|
|
// Decrease size
|
|
|
--mSize;
|
|
|
}
|
|
@@ -627,7 +720,97 @@ public:
|
|
|
std::swap(mControl, ioRHS.mControl);
|
|
|
std::swap(mSize, ioRHS.mSize);
|
|
|
std::swap(mMaxSize, ioRHS.mMaxSize);
|
|
|
- std::swap(mMaxLoad, ioRHS.mMaxLoad);
|
|
|
+ std::swap(mLoadLeft, ioRHS.mLoadLeft);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// In place re-hashing of all elements in the table. Removes all cBucketDeleted elements
|
|
|
+ /// The std version takes a bucket count, but we just re-hash to the same size.
|
|
|
+ void rehash(size_type)
|
|
|
+ {
|
|
|
+ // Update the control value for all buckets
|
|
|
+ for (size_type i = 0; i < mMaxSize; ++i)
|
|
|
+ {
|
|
|
+ uint8 &control = mControl[i];
|
|
|
+ switch (control)
|
|
|
+ {
|
|
|
+ case cBucketDeleted:
|
|
|
+ // Deleted buckets become empty
|
|
|
+ control = cBucketEmpty;
|
|
|
+ break;
|
|
|
+ case cBucketEmpty:
|
|
|
+ // Remains empty
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // Mark all occupied as deleted, to indicate it needs to move to the correct place
|
|
|
+ control = cBucketDeleted;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Replicate control values to the last 15 entries
|
|
|
+ for (size_type i = 0; i < 15; ++i)
|
|
|
+ mControl[mMaxSize + i] = mControl[i];
|
|
|
+
|
|
|
+ // Loop over all elements that have been 'deleted' and move them to their new spot
|
|
|
+ BVec16 bucket_used = BVec16::sReplicate(cBucketUsed);
|
|
|
+ size_type bucket_mask = mMaxSize - 1;
|
|
|
+ uint32 probe_mask = bucket_mask & ~uint32(0b1111); // Mask out lower 4 bits because we test 16 buckets at a time
|
|
|
+ for (size_type src = 0; src < mMaxSize; ++src)
|
|
|
+ if (mControl[src] == cBucketDeleted)
|
|
|
+ for (;;)
|
|
|
+ {
|
|
|
+ // Split hash into index and control value
|
|
|
+ size_type src_index;
|
|
|
+ uint8 src_control;
|
|
|
+ GetIndexAndControlValue(HashTableDetail::sGetKey(mData[src]), src_index, src_control);
|
|
|
+
|
|
|
+ // Linear probing
|
|
|
+ size_type dst = src_index;
|
|
|
+ for (;;)
|
|
|
+ {
|
|
|
+ // Check if any buckets are free
|
|
|
+ BVec16 control_bytes = BVec16::sLoadByte16(mControl + dst);
|
|
|
+ uint32 control_free = uint32(BVec16::sAnd(control_bytes, bucket_used).GetTrues()) ^ 0xffff;
|
|
|
+ if (control_free != 0)
|
|
|
+ {
|
|
|
+ // Select this bucket as destination
|
|
|
+ dst += CountTrailingZeros(control_free);
|
|
|
+ dst &= bucket_mask;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Move to next batch of 16 buckets
|
|
|
+ dst = (dst + 16) & bucket_mask;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if we stay in the same probe group
|
|
|
+ if (((dst - src_index) & probe_mask) == ((src - src_index) & probe_mask))
|
|
|
+ {
|
|
|
+ // We stay in the same group, we can stay where we are
|
|
|
+ SetControlValue(src, src_control);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else if (mControl[dst] == cBucketEmpty)
|
|
|
+ {
|
|
|
+ // There's an empty bucket, move us there
|
|
|
+ SetControlValue(dst, src_control);
|
|
|
+ SetControlValue(src, cBucketEmpty);
|
|
|
+ ::new (mData + dst) KeyValue(std::move(mData[src]));
|
|
|
+ mData[src].~KeyValue();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // There's an element in the bucket we want to move to, swap them
|
|
|
+ JPH_ASSERT(mControl[dst] == cBucketDeleted);
|
|
|
+ SetControlValue(dst, src_control);
|
|
|
+ std::swap(mData[src], mData[dst]);
|
|
|
+ // Iterate again with the same source bucket
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Reinitialize load left
|
|
|
+ mLoadLeft = sGetMaxLoad(mMaxSize) - mSize;
|
|
|
}
|
|
|
|
|
|
private:
|
|
@@ -638,6 +821,10 @@ private:
|
|
|
static constexpr uint64 cMaxLoadFactorNumerator = 7;
|
|
|
static constexpr uint64 cMaxLoadFactorDenominator = 8;
|
|
|
|
|
|
+ /// If we can recover this fraction of deleted elements, we'll reshuffle the buckets in place rather than growing the table
|
|
|
+ static constexpr uint64 cMaxDeletedElementsNumerator = 1;
|
|
|
+ static constexpr uint64 cMaxDeletedElementsDenominator = 8;
|
|
|
+
|
|
|
/// Values that the control bytes can have
|
|
|
static constexpr uint8 cBucketEmpty = 0;
|
|
|
static constexpr uint8 cBucketDeleted = 0x7f;
|
|
@@ -655,8 +842,8 @@ private:
|
|
|
/// Max number of elements that can be stored in the table
|
|
|
size_type mMaxSize = 0;
|
|
|
|
|
|
- /// Max number of elements in the table before it should grow
|
|
|
- size_type mMaxLoad = 0;
|
|
|
+ /// Number of elements we can add to the table before we need to grow
|
|
|
+ size_type mLoadLeft = 0;
|
|
|
};
|
|
|
|
|
|
JPH_NAMESPACE_END
|