mirror of
https://github.com/Open-Cascade-SAS/OCCT.git
synced 2026-07-20 06:09:50 +08:00
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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user