Foundation Classes - Optimize NCollection_FlatMap and NCollection_FlatDataMap internals (#1103)

- Encode slot state in probe distance (`myProbeDistancePlus1`): 0 = empty, >0 = used
  and remove explicit `SlotState`/tombstone handling paths.
- Replace internal `findSlot()` optional-index API with `findSlotIndex()` bool + out index.
- Consolidate insertion logic into `insertRehashedImpl()` variants and reuse cached hash
  during rehash to avoid redundant hash recomputation.
- Tune growth policy to max load factor 13/16 (81.25%) and update `reserve()` math.
- Keep behavior and API intact while reducing per-slot metadata overhead and hot-path branching.
This commit is contained in:
Pasukhin Dmitry
2026-02-20 23:40:49 +00:00
committed by GitHub
parent 82303a99a3
commit 2a09e06d48
4 changed files with 422 additions and 455 deletions

View File

@@ -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<int, int, ConstantHasher> 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<int, int> aMap;

View File

@@ -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<int, ConstantHasher> 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<TCollection_AsciiString> aMap;

View File

@@ -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<const TheItemType*>(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<size_t> 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<size_t> 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<size_t> 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<size_t> 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<size_t> 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<size_t> 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 <typename K, typename V, bool CheckExisting, bool UpdateExisting>
bool insertRehashedImpl(K&& theKey,
V&& theItem,
const size_t theHash,
std::bool_constant<CheckExisting>,
std::bool_constant<UpdateExisting>,
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<K>(theKey);
TheItemType aItemToInsert = std::forward<V>(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 <typename K, typename V>
void insertRehashedImpl(K&& theKey, V&& theItem, const size_t theHash)
{
(void)insertRehashedImpl(std::forward<K>(theKey),
std::forward<V>(theItem),
theHash,
std::false_type{},
std::false_type{});
}
template <typename K, typename V>
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<K>(theKey);
TheItemType aItemToInsert = std::forward<V>(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<K>(theKey),
std::forward<V>(theItem),
aHash,
std::true_type{},
std::true_type{});
}
template <typename K, typename V>
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<K>(theKey);
TheItemType aItemToInsert = std::forward<V>(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<K>(theKey),
std::forward<V>(theItem),
aHash,
std::true_type{},
std::false_type{});
}
template <typename K, typename V, bool IsTry>
TheItemType& insertRefImpl(K&& theKey, V&& theItem, std::bool_constant<IsTry>)
{
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<K>(theKey);
TheItemType aItemToInsert = std::forward<V>(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<K>(theKey),
std::forward<V>(theItem),
aHash,
std::true_type{},
std::false_type{},
&aIndex);
}
else
{
(void)insertRehashedImpl(std::forward<K>(theKey),
std::forward<V>(theItem),
aHash,
std::true_type{},
std::true_type{},
&aIndex);
}
return mySlots[aIndex].Item();
}
template <typename K, bool IsTry, typename... Args>
@@ -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<K>(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<Args>(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<Args>(theArgs)...);
return false;
}
if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance)
if (aProbe > aSlot.ProbeDistance())
{
TheItemType aItemToInsert(std::forward<Args>(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<K>(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<Args>(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<Args>(theArgs)...);
return aSlot.Item();
}
if (aSlot.myState == SlotState::Used && aProbe > aSlot.myProbeDistance)
if (aProbe > aSlot.ProbeDistance())
{
TheItemType aItemToInsert(std::forward<Args>(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:

View File

@@ -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<TheKeyType*>(myKeyStorage); }
const TheKeyType& Key() const noexcept
{
return *reinterpret_cast<const TheKeyType*>(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<size_t> 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<size_t> 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<size_t> 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<size_t> 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<size_t> 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 <typename K, bool CheckExisting>
bool insertRehashedImpl(K&& theKey,
const size_t theHash,
std::bool_constant<CheckExisting>,
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<K>(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 <typename K>
void insertRehashedImpl(K&& theKey, const size_t theHash)
{
(void)insertRehashedImpl(std::forward<K>(theKey), theHash, std::false_type{});
}
template <typename K>
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<K>(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<K>(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<IsTry>)
{
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<K>(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<K>(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: