diff --git a/src/FoundationClasses/TKernel/GTests/NCollection_FlatDataMap_Test.cxx b/src/FoundationClasses/TKernel/GTests/NCollection_FlatDataMap_Test.cxx index d2aa4e1520..d3c453b2cf 100644 --- a/src/FoundationClasses/TKernel/GTests/NCollection_FlatDataMap_Test.cxx +++ b/src/FoundationClasses/TKernel/GTests/NCollection_FlatDataMap_Test.cxx @@ -299,6 +299,31 @@ TEST_F(NCollection_FlatDataMapTest, LargeDataSet) EXPECT_EQ(NUM_ELEMENTS, count); } +TEST_F(NCollection_FlatDataMapTest, LongProbeSequence) +{ + struct ConstantHasher + { + size_t operator()(int) const { return 0; } + + bool operator()(int theKey1, int theKey2) const { return theKey1 == theKey2; } + }; + + constexpr int THE_NUM_ELEMENTS = 400; + NCollection_FlatDataMap aMap; + + for (int i = 0; i < THE_NUM_ELEMENTS; ++i) + { + EXPECT_TRUE(aMap.Bind(i, i * 3)); + } + + EXPECT_EQ(THE_NUM_ELEMENTS, aMap.Size()); + for (int i = 0; i < THE_NUM_ELEMENTS; ++i) + { + EXPECT_TRUE(aMap.IsBound(i)) << "Key " << i << " not found"; + EXPECT_EQ(i * 3, aMap.Find(i)); + } +} + TEST_F(NCollection_FlatDataMapTest, UnBindAndRebind) { NCollection_FlatDataMap aMap; diff --git a/src/FoundationClasses/TKernel/GTests/NCollection_FlatMap_Test.cxx b/src/FoundationClasses/TKernel/GTests/NCollection_FlatMap_Test.cxx index f2b0dcfb27..ac2446f652 100644 --- a/src/FoundationClasses/TKernel/GTests/NCollection_FlatMap_Test.cxx +++ b/src/FoundationClasses/TKernel/GTests/NCollection_FlatMap_Test.cxx @@ -147,6 +147,30 @@ TEST_F(NCollection_FlatMapTest, LargeDataSet) } } +TEST_F(NCollection_FlatMapTest, LongProbeSequence) +{ + struct ConstantHasher + { + size_t operator()(int) const { return 0; } + + bool operator()(int theKey1, int theKey2) const { return theKey1 == theKey2; } + }; + + constexpr int THE_NUM_ELEMENTS = 400; + NCollection_FlatMap aMap; + + for (int i = 0; i < THE_NUM_ELEMENTS; ++i) + { + EXPECT_TRUE(aMap.Add(i)); + } + + EXPECT_EQ(THE_NUM_ELEMENTS, aMap.Size()); + for (int i = 0; i < THE_NUM_ELEMENTS; ++i) + { + EXPECT_TRUE(aMap.Contains(i)) << "Key " << i << " not found"; + } +} + TEST_F(NCollection_FlatMapTest, StringKeys) { NCollection_FlatMap aMap; diff --git a/src/FoundationClasses/TKernel/NCollection/NCollection_FlatDataMap.hxx b/src/FoundationClasses/TKernel/NCollection/NCollection_FlatDataMap.hxx index 6e89362aac..095f42ee42 100644 --- a/src/FoundationClasses/TKernel/NCollection/NCollection_FlatDataMap.hxx +++ b/src/FoundationClasses/TKernel/NCollection/NCollection_FlatDataMap.hxx @@ -48,7 +48,7 @@ * - Keys and values must be movable * - Higher memory usage at low load factors * - Iteration order is not insertion order - * - Maximum probe distance is 250 (sufficient for normal hash distributions) + * - Probe distance grows with collisions (bounded by table capacity) * * @note This class is NOT thread-safe. External synchronization is required * for concurrent access from multiple threads. @@ -70,19 +70,10 @@ public: private: //! Default initial capacity (must be power of 2) static constexpr size_t THE_DEFAULT_CAPACITY = 8; - - //! Maximum allowed probe distance before throwing an exception. - //! This limit is sufficient for normal hash distributions with proper load factors. - static constexpr uint8_t THE_MAX_PROBE_DISTANCE = 250; - - //! Slot state enumeration for hash table entries. - //! Uses Robin Hood hashing with backward shift deletion. - enum class SlotState : uint8_t - { - Empty, //!< Slot has never been used; search can stop here - Deleted, //!< Slot was used but element was removed; search must continue past this - Used //!< Slot contains a valid element - }; + //! Maximum load factor numerator (13/16 = 81.25%). + static constexpr size_t THE_MAX_LOAD_NUMERATOR = 13; + //! Maximum load factor denominator. + static constexpr size_t THE_MAX_LOAD_DENOMINATOR = 16; //! Internal slot structure holding key, value, and metadata. //! Key and item storage is uninitialized until state becomes Used. @@ -94,14 +85,13 @@ private: { alignas(TheKeyType) char myKeyStorage[sizeof(TheKeyType)]; alignas(TheItemType) char myItemStorage[sizeof(TheItemType)]; - size_t myHash; //!< Cached hash code - uint8_t myProbeDistance; //!< Distance from ideal bucket (for Robin Hood) - SlotState myState; //!< Current state of this slot + size_t myHash; //!< Cached hash code + //! Distance from ideal bucket plus one; 0 means Empty, otherwise Used. + size_t myProbeDistancePlus1; Slot() noexcept : myHash(0), - myProbeDistance(0), - myState(SlotState::Empty) + myProbeDistancePlus1(0) { } @@ -118,6 +108,19 @@ private: { return *reinterpret_cast(myItemStorage); } + + bool IsEmpty() const noexcept { return myProbeDistancePlus1 == 0; } + + bool IsUsed() const noexcept { return myProbeDistancePlus1 != 0; } + + size_t ProbeDistance() const noexcept { return myProbeDistancePlus1 - 1; } + + void SetProbeDistance(const size_t theProbeDistance) noexcept + { + myProbeDistancePlus1 = theProbeDistance + 1; + } + + void SetEmpty() noexcept { myProbeDistancePlus1 = 0; } }; #ifdef _MSC_VER #pragma warning(pop) @@ -145,7 +148,7 @@ public: myIndex(0) { // Find first used slot - while (myIndex < myCapacity && mySlots[myIndex].myState != SlotState::Used) + while (myIndex < myCapacity && !mySlots[myIndex].IsUsed()) { ++myIndex; } @@ -158,7 +161,7 @@ public: void Next() noexcept { ++myIndex; - while (myIndex < myCapacity && mySlots[myIndex].myState != SlotState::Used) + while (myIndex < myCapacity && !mySlots[myIndex].IsUsed()) { ++myIndex; } @@ -270,13 +273,12 @@ public: for (size_t i = 0; i < theOther.myCapacity; ++i) { - if (theOther.mySlots[i].myState == SlotState::Used) + if (theOther.mySlots[i].IsUsed()) { new (&mySlots[i].Key()) TheKeyType(theOther.mySlots[i].Key()); new (&mySlots[i].Item()) TheItemType(theOther.mySlots[i].Item()); - mySlots[i].myHash = theOther.mySlots[i].myHash; - mySlots[i].myProbeDistance = theOther.mySlots[i].myProbeDistance; - mySlots[i].myState = SlotState::Used; + mySlots[i].myHash = theOther.mySlots[i].myHash; + mySlots[i].myProbeDistancePlus1 = theOther.mySlots[i].myProbeDistancePlus1; } } mySize = theOther.mySize; @@ -317,13 +319,12 @@ public: for (size_t i = 0; i < theOther.myCapacity; ++i) { - if (theOther.mySlots[i].myState == SlotState::Used) + if (theOther.mySlots[i].IsUsed()) { new (&mySlots[i].Key()) TheKeyType(theOther.mySlots[i].Key()); new (&mySlots[i].Item()) TheItemType(theOther.mySlots[i].Item()); - mySlots[i].myHash = theOther.mySlots[i].myHash; - mySlots[i].myProbeDistance = theOther.mySlots[i].myProbeDistance; - mySlots[i].myState = SlotState::Used; + mySlots[i].myHash = theOther.mySlots[i].myHash; + mySlots[i].myProbeDistancePlus1 = theOther.mySlots[i].myProbeDistancePlus1; } } mySize = theOther.mySize; @@ -369,7 +370,8 @@ public: { if (mySize == 0) return false; - return findSlot(theKey).has_value(); + size_t anIndex = 0; + return findSlotIndex(theKey, anIndex); } //! Contained returns optional pair of const references to key and value. @@ -380,10 +382,10 @@ public: { if (mySize == 0) return std::nullopt; - const std::optional aIdx = findSlot(theKey); - if (!aIdx.has_value()) + size_t aIdx = 0; + if (!findSlotIndex(theKey, aIdx)) return std::nullopt; - return std::make_pair(std::cref(mySlots[*aIdx].Key()), std::cref(mySlots[*aIdx].Item())); + return std::make_pair(std::cref(mySlots[aIdx].Key()), std::cref(mySlots[aIdx].Item())); } //! Contained returns optional pair of const key reference and mutable value reference. @@ -394,10 +396,10 @@ public: { if (mySize == 0) return std::nullopt; - const std::optional aIdx = findSlot(theKey); - if (!aIdx.has_value()) + size_t aIdx = 0; + if (!findSlotIndex(theKey, aIdx)) return std::nullopt; - return std::make_pair(std::cref(mySlots[*aIdx].Key()), std::ref(mySlots[*aIdx].Item())); + return std::make_pair(std::cref(mySlots[aIdx].Key()), std::ref(mySlots[aIdx].Item())); } //! Find value by key, returns nullptr if not found @@ -405,10 +407,10 @@ public: { if (mySize == 0) return nullptr; - const std::optional aFoundIndex = findSlot(theKey); - if (aFoundIndex.has_value()) + size_t aFoundIndex = 0; + if (findSlotIndex(theKey, aFoundIndex)) { - return &mySlots[*aFoundIndex].Item(); + return &mySlots[aFoundIndex].Item(); } return nullptr; } @@ -418,10 +420,10 @@ public: { if (mySize == 0) return nullptr; - const std::optional aFoundIndex = findSlot(theKey); - if (aFoundIndex.has_value()) + size_t aFoundIndex = 0; + if (findSlotIndex(theKey, aFoundIndex)) { - return &mySlots[*aFoundIndex].Item(); + return &mySlots[aFoundIndex].Item(); } return nullptr; } @@ -630,17 +632,17 @@ public: if (mySize == 0) return false; - const std::optional aFoundIndex = findSlot(theKey); - if (!aFoundIndex.has_value()) + size_t aFoundIndex = 0; + if (!findSlotIndex(theKey, aFoundIndex)) { return false; } - const size_t aIndex = *aFoundIndex; + const size_t aIndex = aFoundIndex; mySlots[aIndex].Key().~TheKeyType(); mySlots[aIndex].Item().~TheItemType(); - mySlots[aIndex].myState = SlotState::Deleted; + mySlots[aIndex].SetEmpty(); --mySize; backwardShiftDelete(aIndex); @@ -656,15 +658,11 @@ public: { for (size_t i = 0; i < myCapacity; ++i) { - if (mySlots[i].myState == SlotState::Used) + if (mySlots[i].IsUsed()) { mySlots[i].Key().~TheKeyType(); mySlots[i].Item().~TheItemType(); - mySlots[i].myState = SlotState::Empty; - } - else if (mySlots[i].myState == SlotState::Deleted) - { - mySlots[i].myState = SlotState::Empty; + mySlots[i].SetEmpty(); } } mySize = 0; @@ -693,7 +691,9 @@ public: //! Reserve capacity for at least theN elements void reserve(size_t theN) { - size_t aNewCapacity = nextPowerOf2(theN + theN / 8); // ~87.5% load factor target + const size_t aMinCapacity = + (theN * THE_MAX_LOAD_DENOMINATOR + THE_MAX_LOAD_NUMERATOR - 1) / THE_MAX_LOAD_NUMERATOR; + size_t aNewCapacity = nextPowerOf2(aMinCapacity); if (aNewCapacity > myCapacity) { rehash(aNewCapacity); @@ -785,8 +785,9 @@ private: //! Ensure there's room for at least one more element void ensureCapacity() { - // Grow at ~87.5% load factor - if (myCapacity == 0 || (mySize + 1) * 8 > myCapacity * 7) + // Grow at ~81.25% load factor. + if (myCapacity == 0 + || (mySize + 1) * THE_MAX_LOAD_DENOMINATOR > myCapacity * THE_MAX_LOAD_NUMERATOR) { size_t aNewCapacity = myCapacity == 0 ? THE_DEFAULT_CAPACITY : myCapacity * 2; rehash(aNewCapacity); @@ -812,9 +813,11 @@ private: { for (size_t i = 0; i < aOldCapacity; ++i) { - if (aOldSlots[i].myState == SlotState::Used) + if (aOldSlots[i].IsUsed()) { - insertImpl(std::move(aOldSlots[i].Key()), std::move(aOldSlots[i].Item())); + insertRehashedImpl(std::move(aOldSlots[i].Key()), + std::move(aOldSlots[i].Item()), + aOldSlots[i].myHash); aOldSlots[i].Key().~TheKeyType(); aOldSlots[i].Item().~TheItemType(); } @@ -825,201 +828,169 @@ private: //! Find slot containing key. //! @param theKey key to find - //! @return index of found slot, or std::nullopt if not found - std::optional findSlot(const TheKeyType& theKey) const + //! @param[out] theIndex found index + //! @return true if key was found + bool findSlotIndex(const TheKeyType& theKey, size_t& theIndex) const { const size_t aHash = myHasher(theKey); const size_t aMask = myCapacity - 1; size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; + size_t aProbe = 0; const size_t aMaxProbe = myCapacity; while (aProbe < aMaxProbe) { const Slot& aSlot = mySlots[aIndex]; - if (aSlot.myState == SlotState::Empty) + if (aSlot.IsEmpty()) { - return std::nullopt; + return false; } - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHash - && myHasher(aSlot.Key(), theKey)) + if (aSlot.myHash == aHash && myHasher(aSlot.Key(), theKey)) { - return aIndex; + theIndex = aIndex; + return true; } - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) + if (aProbe > aSlot.ProbeDistance()) { - return std::nullopt; + return false; } ++aProbe; aIndex = (aIndex + 1) & aMask; } - return std::nullopt; + return false; + } + + template + bool insertRehashedImpl(K&& theKey, + V&& theItem, + const size_t theHash, + std::bool_constant, + std::bool_constant, + size_t* theInsertedIndex = nullptr) + { + const size_t aMask = myCapacity - 1; + size_t aIndex = theHash & aMask; + size_t aProbe = 0; + size_t anInsertedIndex = 0; + bool aHasInsertedIndex = false; + + TheKeyType aKeyToInsert = std::forward(theKey); + TheItemType aItemToInsert = std::forward(theItem); + size_t aHashToInsert = theHash; + + while (true) + { + Slot& aSlot = mySlots[aIndex]; + if (aSlot.IsEmpty()) + { + new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); + new (&aSlot.Item()) TheItemType(std::move(aItemToInsert)); + aSlot.myHash = aHashToInsert; + aSlot.SetProbeDistance(aProbe); + ++mySize; + if (theInsertedIndex != nullptr) + { + *theInsertedIndex = aHasInsertedIndex ? anInsertedIndex : aIndex; + } + return true; + } + + if constexpr (CheckExisting) + { + if (aSlot.myHash == aHashToInsert && myHasher(aSlot.Key(), aKeyToInsert)) + { + if constexpr (UpdateExisting) + { + aSlot.Item() = std::move(aItemToInsert); + } + if (theInsertedIndex != nullptr) + { + *theInsertedIndex = aIndex; + } + return false; + } + } + + if (aProbe > aSlot.ProbeDistance()) + { + std::swap(aKeyToInsert, aSlot.Key()); + std::swap(aItemToInsert, aSlot.Item()); + std::swap(aHashToInsert, aSlot.myHash); + const size_t aTmp = aProbe; + aProbe = aSlot.ProbeDistance(); + aSlot.SetProbeDistance(aTmp); + if (!aHasInsertedIndex) + { + anInsertedIndex = aIndex; + aHasInsertedIndex = true; + } + } + + ++aProbe; + aIndex = (aIndex + 1) & aMask; + } + } + + template + void insertRehashedImpl(K&& theKey, V&& theItem, const size_t theHash) + { + (void)insertRehashedImpl(std::forward(theKey), + std::forward(theItem), + theHash, + std::false_type{}, + std::false_type{}); } template bool insertImpl(K&& theKey, V&& theItem) { - const size_t aHash = myHasher(theKey); - const size_t aMask = myCapacity - 1; - size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; - - TheKeyType aKeyToInsert = std::forward(theKey); - TheItemType aItemToInsert = std::forward(theItem); - size_t aHashToInsert = aHash; - - while (true) - { - Slot& aSlot = mySlots[aIndex]; - - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) - { - new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); - new (&aSlot.Item()) TheItemType(std::move(aItemToInsert)); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; - ++mySize; - return true; - } - - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) - { - aSlot.Item() = std::move(aItemToInsert); - return false; - } - - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) - { - std::swap(aKeyToInsert, aSlot.Key()); - std::swap(aItemToInsert, aSlot.Item()); - std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; - } - - ++aProbe; - aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } - } + const size_t aHash = myHasher(theKey); + return insertRehashedImpl(std::forward(theKey), + std::forward(theItem), + aHash, + std::true_type{}, + std::true_type{}); } template bool tryInsertImpl(K&& theKey, V&& theItem) { - const size_t aHash = myHasher(theKey); - const size_t aMask = myCapacity - 1; - size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; - - TheKeyType aKeyToInsert = std::forward(theKey); - TheItemType aItemToInsert = std::forward(theItem); - size_t aHashToInsert = aHash; - - while (true) - { - Slot& aSlot = mySlots[aIndex]; - - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) - { - new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); - new (&aSlot.Item()) TheItemType(std::move(aItemToInsert)); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; - ++mySize; - return true; - } - - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) - { - return false; - } - - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) - { - std::swap(aKeyToInsert, aSlot.Key()); - std::swap(aItemToInsert, aSlot.Item()); - std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; - } - - ++aProbe; - aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } - } + const size_t aHash = myHasher(theKey); + return insertRehashedImpl(std::forward(theKey), + std::forward(theItem), + aHash, + std::true_type{}, + std::false_type{}); } template TheItemType& insertRefImpl(K&& theKey, V&& theItem, std::bool_constant) { const size_t aHash = myHasher(theKey); - const size_t aMask = myCapacity - 1; - size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; - - TheKeyType aKeyToInsert = std::forward(theKey); - TheItemType aItemToInsert = std::forward(theItem); - size_t aHashToInsert = aHash; - - while (true) + size_t aIndex = 0; + if constexpr (IsTry) { - Slot& aSlot = mySlots[aIndex]; - - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) - { - new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); - new (&aSlot.Item()) TheItemType(std::move(aItemToInsert)); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; - ++mySize; - return aSlot.Item(); - } - - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) - { - if constexpr (!IsTry) - aSlot.Item() = std::move(aItemToInsert); - return aSlot.Item(); - } - - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) - { - std::swap(aKeyToInsert, aSlot.Key()); - std::swap(aItemToInsert, aSlot.Item()); - std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; - } - - ++aProbe; - aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } + (void)insertRehashedImpl(std::forward(theKey), + std::forward(theItem), + aHash, + std::true_type{}, + std::false_type{}, + &aIndex); } + else + { + (void)insertRehashedImpl(std::forward(theKey), + std::forward(theItem), + aHash, + std::true_type{}, + std::true_type{}, + &aIndex); + } + return mySlots[aIndex].Item(); } template @@ -1028,7 +999,7 @@ private: const size_t aHash = myHasher(theKey); const size_t aMask = myCapacity - 1; size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; + size_t aProbe = 0; TheKeyType aKeyToInsert = std::forward(theKey); size_t aHashToInsert = aHash; @@ -1037,35 +1008,33 @@ private: { Slot& aSlot = mySlots[aIndex]; - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) + if (aSlot.IsEmpty()) { new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); new (&aSlot.Item()) TheItemType(std::forward(theArgs)...); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; + aSlot.myHash = aHashToInsert; + aSlot.SetProbeDistance(aProbe); ++mySize; return true; } - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) + if (aSlot.myHash == aHashToInsert && myHasher(aSlot.Key(), aKeyToInsert)) { if constexpr (!IsTry) aSlot.Item() = TheItemType(std::forward(theArgs)...); return false; } - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) + if (aProbe > aSlot.ProbeDistance()) { TheItemType aItemToInsert(std::forward(theArgs)...); std::swap(aKeyToInsert, aSlot.Key()); std::swap(aItemToInsert, aSlot.Item()); std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; + const size_t aTmp = aProbe; + aProbe = aSlot.ProbeDistance(); + aSlot.SetProbeDistance(aTmp); ++aProbe; aIndex = (aIndex + 1) & aMask; @@ -1074,44 +1043,33 @@ private: { Slot& aSlot2 = mySlots[aIndex]; - if (aSlot2.myState == SlotState::Empty || aSlot2.myState == SlotState::Deleted) + if (aSlot2.IsEmpty()) { new (&aSlot2.Key()) TheKeyType(std::move(aKeyToInsert)); new (&aSlot2.Item()) TheItemType(std::move(aItemToInsert)); - aSlot2.myHash = aHashToInsert; - aSlot2.myProbeDistance = aProbe; - aSlot2.myState = SlotState::Used; + aSlot2.myHash = aHashToInsert; + aSlot2.SetProbeDistance(aProbe); ++mySize; return true; } - if (aSlot2.myState == SlotState::Used && aProbe > aSlot2.myProbeDistance) + if (aProbe > aSlot2.ProbeDistance()) { std::swap(aKeyToInsert, aSlot2.Key()); std::swap(aItemToInsert, aSlot2.Item()); std::swap(aHashToInsert, aSlot2.myHash); - uint8_t aTmp2 = aProbe; - aProbe = aSlot2.myProbeDistance; - aSlot2.myProbeDistance = aTmp2; + const size_t aTmp2 = aProbe; + aProbe = aSlot2.ProbeDistance(); + aSlot2.SetProbeDistance(aTmp2); } ++aProbe; aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } } } ++aProbe; aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } } } @@ -1121,7 +1079,7 @@ private: const size_t aHash = myHasher(theKey); const size_t aMask = myCapacity - 1; size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; + size_t aProbe = 0; TheKeyType aKeyToInsert = std::forward(theKey); size_t aHashToInsert = aHash; @@ -1130,35 +1088,33 @@ private: { Slot& aSlot = mySlots[aIndex]; - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) + if (aSlot.IsEmpty()) { new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); new (&aSlot.Item()) TheItemType(std::forward(theArgs)...); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; + aSlot.myHash = aHashToInsert; + aSlot.SetProbeDistance(aProbe); ++mySize; return aSlot.Item(); } - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) + if (aSlot.myHash == aHashToInsert && myHasher(aSlot.Key(), aKeyToInsert)) { if constexpr (!IsTry) aSlot.Item() = TheItemType(std::forward(theArgs)...); return aSlot.Item(); } - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) + if (aProbe > aSlot.ProbeDistance()) { TheItemType aItemToInsert(std::forward(theArgs)...); std::swap(aKeyToInsert, aSlot.Key()); std::swap(aItemToInsert, aSlot.Item()); std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; + const size_t aTmp = aProbe; + aProbe = aSlot.ProbeDistance(); + aSlot.SetProbeDistance(aTmp); TheItemType& aResult = aSlot.Item(); @@ -1169,44 +1125,33 @@ private: { Slot& aSlot2 = mySlots[aIndex]; - if (aSlot2.myState == SlotState::Empty || aSlot2.myState == SlotState::Deleted) + if (aSlot2.IsEmpty()) { new (&aSlot2.Key()) TheKeyType(std::move(aKeyToInsert)); new (&aSlot2.Item()) TheItemType(std::move(aItemToInsert)); - aSlot2.myHash = aHashToInsert; - aSlot2.myProbeDistance = aProbe; - aSlot2.myState = SlotState::Used; + aSlot2.myHash = aHashToInsert; + aSlot2.SetProbeDistance(aProbe); ++mySize; return aResult; } - if (aSlot2.myState == SlotState::Used && aProbe > aSlot2.myProbeDistance) + if (aProbe > aSlot2.ProbeDistance()) { std::swap(aKeyToInsert, aSlot2.Key()); std::swap(aItemToInsert, aSlot2.Item()); std::swap(aHashToInsert, aSlot2.myHash); - uint8_t aTmp2 = aProbe; - aProbe = aSlot2.myProbeDistance; - aSlot2.myProbeDistance = aTmp2; + const size_t aTmp2 = aProbe; + aProbe = aSlot2.ProbeDistance(); + aSlot2.SetProbeDistance(aTmp2); } ++aProbe; aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } } } ++aProbe; aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatDataMap: excessive probe length"); - } } } @@ -1216,13 +1161,12 @@ private: size_t aCurrent = theIndex; size_t aNext = (aCurrent + 1) & aMask; - while (mySlots[aNext].myState == SlotState::Used && mySlots[aNext].myProbeDistance > 0) + while (mySlots[aNext].IsUsed() && mySlots[aNext].ProbeDistance() > 0) { new (&mySlots[aCurrent].Key()) TheKeyType(std::move(mySlots[aNext].Key())); new (&mySlots[aCurrent].Item()) TheItemType(std::move(mySlots[aNext].Item())); - mySlots[aCurrent].myHash = mySlots[aNext].myHash; - mySlots[aCurrent].myProbeDistance = mySlots[aNext].myProbeDistance - 1; - mySlots[aCurrent].myState = SlotState::Used; + mySlots[aCurrent].myHash = mySlots[aNext].myHash; + mySlots[aCurrent].SetProbeDistance(mySlots[aNext].ProbeDistance() - 1); mySlots[aNext].Key().~TheKeyType(); mySlots[aNext].Item().~TheItemType(); @@ -1231,7 +1175,7 @@ private: aNext = (aNext + 1) & aMask; } - mySlots[aCurrent].myState = SlotState::Empty; + mySlots[aCurrent].SetEmpty(); } private: diff --git a/src/FoundationClasses/TKernel/NCollection/NCollection_FlatMap.hxx b/src/FoundationClasses/TKernel/NCollection/NCollection_FlatMap.hxx index fdfb3d5319..0ec351d03a 100644 --- a/src/FoundationClasses/TKernel/NCollection/NCollection_FlatMap.hxx +++ b/src/FoundationClasses/TKernel/NCollection/NCollection_FlatMap.hxx @@ -46,7 +46,7 @@ * - Keys must be movable * - Higher memory usage at low load factors * - Iteration order is not insertion order - * - Maximum probe distance is 250 (sufficient for normal hash distributions) + * - Probe distance grows with collisions (bounded by table capacity) * * @note This class is NOT thread-safe. External synchronization is required * for concurrent access from multiple threads. @@ -64,44 +64,47 @@ public: private: //! Default initial capacity (must be power of 2) static constexpr size_t THE_DEFAULT_CAPACITY = 8; - - //! Maximum allowed probe distance before throwing an exception. - //! This limit is sufficient for normal hash distributions with proper load factors. - static constexpr uint8_t THE_MAX_PROBE_DISTANCE = 250; - - //! Slot state enumeration for hash table entries. - //! Uses Robin Hood hashing with backward shift deletion. - enum class SlotState : uint8_t - { - Empty, //!< Slot has never been used; search can stop here - Deleted, //!< Slot was used but element was removed; search must continue past this - Used //!< Slot contains a valid element - }; + //! Maximum load factor numerator (13/16 = 81.25%). + static constexpr size_t THE_MAX_LOAD_NUMERATOR = 13; + //! Maximum load factor denominator. + static constexpr size_t THE_MAX_LOAD_DENOMINATOR = 16; //! Internal slot structure holding key and metadata. //! Key storage is uninitialized until state becomes Used. struct Slot { alignas(TheKeyType) char myKeyStorage[sizeof(TheKeyType)]; //!< Uninitialized key storage - size_t myHash; //!< Cached hash code - uint8_t myProbeDistance; //!< Distance from ideal bucket (for Robin Hood) - SlotState myState; //!< Current state of this slot + size_t myHash; //!< Cached hash code + //! Distance from ideal bucket plus one; 0 means Empty, otherwise Used. + size_t myProbeDistancePlus1; Slot() noexcept : myHash(0), - myProbeDistance(0), - myState(SlotState::Empty) + myProbeDistancePlus1(0) { // Key is NOT constructed - myKeyStorage is uninitialized } - //! Access the key (only valid when myState == Used) + //! Access the key (only valid when IsUsed() == true) TheKeyType& Key() noexcept { return *reinterpret_cast(myKeyStorage); } const TheKeyType& Key() const noexcept { return *reinterpret_cast(myKeyStorage); } + + bool IsEmpty() const noexcept { return myProbeDistancePlus1 == 0; } + + bool IsUsed() const noexcept { return myProbeDistancePlus1 != 0; } + + size_t ProbeDistance() const noexcept { return myProbeDistancePlus1 - 1; } + + void SetProbeDistance(const size_t theProbeDistance) noexcept + { + myProbeDistancePlus1 = theProbeDistance + 1; + } + + void SetEmpty() noexcept { myProbeDistancePlus1 = 0; } }; public: @@ -126,7 +129,7 @@ public: myIndex(0) { // Find first used slot - while (myIndex < myCapacity && mySlots[myIndex].myState != SlotState::Used) + while (myIndex < myCapacity && !mySlots[myIndex].IsUsed()) { ++myIndex; } @@ -139,7 +142,7 @@ public: void Next() noexcept { ++myIndex; - while (myIndex < myCapacity && mySlots[myIndex].myState != SlotState::Used) + while (myIndex < myCapacity && !mySlots[myIndex].IsUsed()) { ++myIndex; } @@ -239,12 +242,11 @@ public: for (size_t i = 0; i < theOther.myCapacity; ++i) { - if (theOther.mySlots[i].myState == SlotState::Used) + if (theOther.mySlots[i].IsUsed()) { new (&mySlots[i].Key()) TheKeyType(theOther.mySlots[i].Key()); - mySlots[i].myHash = theOther.mySlots[i].myHash; - mySlots[i].myProbeDistance = theOther.mySlots[i].myProbeDistance; - mySlots[i].myState = SlotState::Used; + mySlots[i].myHash = theOther.mySlots[i].myHash; + mySlots[i].myProbeDistancePlus1 = theOther.mySlots[i].myProbeDistancePlus1; } } mySize = theOther.mySize; @@ -285,12 +287,11 @@ public: for (size_t i = 0; i < theOther.myCapacity; ++i) { - if (theOther.mySlots[i].myState == SlotState::Used) + if (theOther.mySlots[i].IsUsed()) { new (&mySlots[i].Key()) TheKeyType(theOther.mySlots[i].Key()); - mySlots[i].myHash = theOther.mySlots[i].myHash; - mySlots[i].myProbeDistance = theOther.mySlots[i].myProbeDistance; - mySlots[i].myState = SlotState::Used; + mySlots[i].myHash = theOther.mySlots[i].myHash; + mySlots[i].myProbeDistancePlus1 = theOther.mySlots[i].myProbeDistancePlus1; } } mySize = theOther.mySize; @@ -336,7 +337,8 @@ public: { if (mySize == 0) return false; - return findSlot(theKey).has_value(); + size_t anIndex = 0; + return findSlotIndex(theKey, anIndex); } //! Contained returns optional const reference to the key in the map. @@ -345,10 +347,10 @@ public: { if (mySize == 0) return std::nullopt; - const std::optional aIdx = findSlot(theKey); - if (!aIdx.has_value()) + size_t aIdx = 0; + if (!findSlotIndex(theKey, aIdx)) return std::nullopt; - return std::cref(mySlots[*aIdx].Key()); + return std::cref(mySlots[aIdx].Key()); } //! Seek returns pointer to key in map. Returns NULL if not found. @@ -356,10 +358,10 @@ public: { if (mySize == 0) return nullptr; - const std::optional aIdx = findSlot(theKey); - if (!aIdx.has_value()) + size_t aIdx = 0; + if (!findSlotIndex(theKey, aIdx)) return nullptr; - return &mySlots[*aIdx].Key(); + return &mySlots[aIdx].Key(); } //! ChangeSeek returns modifiable pointer to key in map. Returns NULL if not found. @@ -367,10 +369,10 @@ public: { if (mySize == 0) return nullptr; - const std::optional aIdx = findSlot(theKey); - if (!aIdx.has_value()) + size_t aIdx = 0; + if (!findSlotIndex(theKey, aIdx)) return nullptr; - return &mySlots[*aIdx].Key(); + return &mySlots[aIdx].Key(); } public: @@ -462,17 +464,17 @@ public: if (mySize == 0) return false; - const std::optional aFoundIndex = findSlot(theKey); - if (!aFoundIndex.has_value()) + size_t aFoundIndex = 0; + if (!findSlotIndex(theKey, aFoundIndex)) { return false; } - const size_t aIndex = *aFoundIndex; + const size_t aIndex = aFoundIndex; // Destroy key mySlots[aIndex].Key().~TheKeyType(); - mySlots[aIndex].myState = SlotState::Deleted; + mySlots[aIndex].SetEmpty(); --mySize; // Backward shift delete @@ -488,14 +490,10 @@ public: { for (size_t i = 0; i < myCapacity; ++i) { - if (mySlots[i].myState == SlotState::Used) + if (mySlots[i].IsUsed()) { mySlots[i].Key().~TheKeyType(); - mySlots[i].myState = SlotState::Empty; - } - else if (mySlots[i].myState == SlotState::Deleted) - { - mySlots[i].myState = SlotState::Empty; + mySlots[i].SetEmpty(); } } mySize = 0; @@ -524,7 +522,9 @@ public: //! Reserve capacity for at least theN elements void reserve(size_t theN) { - size_t aNewCapacity = nextPowerOf2(theN + theN / 8); + const size_t aMinCapacity = + (theN * THE_MAX_LOAD_DENOMINATOR + THE_MAX_LOAD_NUMERATOR - 1) / THE_MAX_LOAD_NUMERATOR; + size_t aNewCapacity = nextPowerOf2(aMinCapacity); if (aNewCapacity > myCapacity) { rehash(aNewCapacity); @@ -564,8 +564,9 @@ private: void ensureCapacity() { - // Grow at ~87.5% load factor - if (myCapacity == 0 || (mySize + 1) * 8 > myCapacity * 7) + // Grow at ~81.25% load factor. + if (myCapacity == 0 + || (mySize + 1) * THE_MAX_LOAD_DENOMINATOR > myCapacity * THE_MAX_LOAD_NUMERATOR) { size_t aNewCapacity = myCapacity == 0 ? THE_DEFAULT_CAPACITY : myCapacity * 2; rehash(aNewCapacity); @@ -589,9 +590,9 @@ private: { for (size_t i = 0; i < aOldCapacity; ++i) { - if (aOldSlots[i].myState == SlotState::Used) + if (aOldSlots[i].IsUsed()) { - insertImpl(std::move(aOldSlots[i].Key())); + insertRehashedImpl(std::move(aOldSlots[i].Key()), aOldSlots[i].myHash); aOldSlots[i].Key().~TheKeyType(); } } @@ -601,90 +602,116 @@ private: //! Find slot containing key. //! @param theKey key to find - //! @return index of found slot, or std::nullopt if not found - std::optional findSlot(const TheKeyType& theKey) const + //! @param[out] theIndex found index + //! @return true if key was found + bool findSlotIndex(const TheKeyType& theKey, size_t& theIndex) const { const size_t aHash = myHasher(theKey); const size_t aMask = myCapacity - 1; size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; + size_t aProbe = 0; const size_t aMaxProbe = myCapacity; while (aProbe < aMaxProbe) { const Slot& aSlot = mySlots[aIndex]; - if (aSlot.myState == SlotState::Empty) + if (aSlot.IsEmpty()) { - return std::nullopt; + return false; } - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHash - && myHasher(aSlot.Key(), theKey)) + if (aSlot.myHash == aHash && myHasher(aSlot.Key(), theKey)) { - return aIndex; + theIndex = aIndex; + return true; } // Robin Hood optimization: if current probe > slot's probe, key can't exist further - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) + if (aProbe > aSlot.ProbeDistance()) { - return std::nullopt; + return false; } ++aProbe; aIndex = (aIndex + 1) & aMask; } - return std::nullopt; + return false; + } + + template + bool insertRehashedImpl(K&& theKey, + const size_t theHash, + std::bool_constant, + size_t* theInsertedIndex = nullptr) + { + const size_t aMask = myCapacity - 1; + size_t aIndex = theHash & aMask; + size_t aProbe = 0; + size_t anInsertedIndex = 0; + bool aHasInsertedIndex = false; + + TheKeyType aKeyToInsert = std::forward(theKey); + size_t aHashToInsert = theHash; + + while (true) + { + Slot& aSlot = mySlots[aIndex]; + if (aSlot.IsEmpty()) + { + new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); + aSlot.myHash = aHashToInsert; + aSlot.SetProbeDistance(aProbe); + ++mySize; + if (theInsertedIndex != nullptr) + { + *theInsertedIndex = aHasInsertedIndex ? anInsertedIndex : aIndex; + } + return true; + } + + if constexpr (CheckExisting) + { + if (aSlot.myHash == aHashToInsert && myHasher(aSlot.Key(), aKeyToInsert)) + { + if (theInsertedIndex != nullptr) + { + *theInsertedIndex = aIndex; + } + return false; + } + } + + if (aProbe > aSlot.ProbeDistance()) + { + std::swap(aKeyToInsert, aSlot.Key()); + std::swap(aHashToInsert, aSlot.myHash); + const size_t aTmp = aProbe; + aProbe = aSlot.ProbeDistance(); + aSlot.SetProbeDistance(aTmp); + if (!aHasInsertedIndex) + { + anInsertedIndex = aIndex; + aHasInsertedIndex = true; + } + } + + ++aProbe; + aIndex = (aIndex + 1) & aMask; + } + } + + template + void insertRehashedImpl(K&& theKey, const size_t theHash) + { + (void)insertRehashedImpl(std::forward(theKey), theHash, std::false_type{}); } template bool insertImpl(K&& theKey) { - const size_t aHash = myHasher(theKey); - const size_t aMask = myCapacity - 1; - size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; - - TheKeyType aKeyToInsert = std::forward(theKey); - size_t aHashToInsert = aHash; - - while (true) - { - Slot& aSlot = mySlots[aIndex]; - - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) - { - new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; - ++mySize; - return true; - } - - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) - { - return false; // Already exists - } - - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) - { - std::swap(aKeyToInsert, aSlot.Key()); - std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; - } - - ++aProbe; - aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatMap: excessive probe length"); - } - } + const size_t aHash = myHasher(theKey); + return insertRehashedImpl(std::forward(theKey), aHash, std::true_type{}); } //! Insert key and return reference to it (for Added method) @@ -693,56 +720,9 @@ private: const TheKeyType& insertRefImpl(K&& theKey, std::bool_constant) { const size_t aHash = myHasher(theKey); - const size_t aMask = myCapacity - 1; - size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; - - TheKeyType aKeyToInsert = std::forward(theKey); - size_t aHashToInsert = aHash; - size_t aFoundIndex = SIZE_MAX; - - while (true) - { - Slot& aSlot = mySlots[aIndex]; - - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) - { - new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; - ++mySize; - return aSlot.Key(); - } - - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) - { - return aSlot.Key(); // Already exists - } - - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) - { - // Track where the original key ends up after swaps - if (aFoundIndex == SIZE_MAX) - { - aFoundIndex = aIndex; - } - std::swap(aKeyToInsert, aSlot.Key()); - std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; - } - - ++aProbe; - aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - { - throw Standard_OutOfRange("NCollection_FlatMap: excessive probe length"); - } - } + size_t aIndex = 0; + (void)insertRehashedImpl(std::forward(theKey), aHash, std::true_type{}, &aIndex); + return mySlots[aIndex].Key(); } //! Implementation helper for Emplace/Emplaced. @@ -755,7 +735,7 @@ private: const size_t aHash = myHasher(theKey); const size_t aMask = myCapacity - 1; size_t aIndex = aHash & aMask; - uint8_t aProbe = 0; + size_t aProbe = 0; TheKeyType aKeyToInsert = std::move(theKey); size_t aHashToInsert = aHash; @@ -764,12 +744,11 @@ private: { Slot& aSlot = mySlots[aIndex]; - if (aSlot.myState == SlotState::Empty || aSlot.myState == SlotState::Deleted) + if (aSlot.IsEmpty()) { new (&aSlot.Key()) TheKeyType(std::move(aKeyToInsert)); - aSlot.myHash = aHashToInsert; - aSlot.myProbeDistance = aProbe; - aSlot.myState = SlotState::Used; + aSlot.myHash = aHashToInsert; + aSlot.SetProbeDistance(aProbe); ++mySize; if constexpr (ReturnRef) return aSlot.Key(); @@ -777,8 +756,7 @@ private: return true; } - if (aSlot.myState == SlotState::Used && aSlot.myHash == aHashToInsert - && myHasher(aSlot.Key(), aKeyToInsert)) + if (aSlot.myHash == aHashToInsert && myHasher(aSlot.Key(), aKeyToInsert)) { if constexpr (!IsTry) aSlot.Key() = std::move(aKeyToInsert); @@ -788,20 +766,17 @@ private: return false; } - if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance) + if (aProbe > aSlot.ProbeDistance()) { std::swap(aKeyToInsert, aSlot.Key()); std::swap(aHashToInsert, aSlot.myHash); - uint8_t aTmp = aProbe; - aProbe = aSlot.myProbeDistance; - aSlot.myProbeDistance = aTmp; + const size_t aTmp = aProbe; + aProbe = aSlot.ProbeDistance(); + aSlot.SetProbeDistance(aTmp); } ++aProbe; aIndex = (aIndex + 1) & aMask; - - if (aProbe > THE_MAX_PROBE_DISTANCE) - throw Standard_OutOfRange("NCollection_FlatMap: excessive probe length"); } } @@ -811,13 +786,12 @@ private: size_t aCurrent = theIndex; size_t aNext = (aCurrent + 1) & aMask; - while (mySlots[aNext].myState == SlotState::Used && mySlots[aNext].myProbeDistance > 0) + while (mySlots[aNext].IsUsed() && mySlots[aNext].ProbeDistance() > 0) { // Construct key at aCurrent (which was destroyed or never had a key) new (&mySlots[aCurrent].Key()) TheKeyType(std::move(mySlots[aNext].Key())); - mySlots[aCurrent].myHash = mySlots[aNext].myHash; - mySlots[aCurrent].myProbeDistance = mySlots[aNext].myProbeDistance - 1; - mySlots[aCurrent].myState = SlotState::Used; + mySlots[aCurrent].myHash = mySlots[aNext].myHash; + mySlots[aCurrent].SetProbeDistance(mySlots[aNext].ProbeDistance() - 1); // Destroy the moved-from key at aNext mySlots[aNext].Key().~TheKeyType(); @@ -828,7 +802,7 @@ private: // Mark final slot as Empty (removes tombstone; either original deleted slot or last // shifted-from slot) - mySlots[aCurrent].myState = SlotState::Empty; + mySlots[aCurrent].SetEmpty(); } private: