Skip to content

Commit 790af83

Browse files
committed
Bug 1460674 - part 3 - make PLDHashTable iteration faster; r=njn
The core loop of Iterator::Next() requires multiple branches, one to check for entry liveness and one to check for wraparound. We can rewrite this to use masking instead, as well as iterating only over the hashes, and reconstructing the entry pointer when we know we've reached a live entry. This change cuts the time taken on the collections benchmark by the iteration portion in half.
1 parent 529e924 commit 790af83

File tree

2 files changed

+45
-26
lines changed

2 files changed

+45
-26
lines changed

xpcom/ds/PLDHashTable.cpp

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,6 @@ PLDHashTable::ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
746746

747747
PLDHashTable::Iterator::Iterator(Iterator&& aOther)
748748
: mTable(aOther.mTable)
749-
, mLimit(aOther.mLimit)
750749
, mCurrent(aOther.mCurrent)
751750
, mNexts(aOther.mNexts)
752751
, mNextsLimit(aOther.mNextsLimit)
@@ -755,7 +754,7 @@ PLDHashTable::Iterator::Iterator(Iterator&& aOther)
755754
{
756755
// No need to change |mChecker| here.
757756
aOther.mTable = nullptr;
758-
// We don't really have the concept of a null slot, so leave mLimit/mCurrent.
757+
// We don't really have the concept of a null slot, so leave mCurrent.
759758
aOther.mNexts = 0;
760759
aOther.mNextsLimit = 0;
761760
aOther.mHaveRemoved = false;
@@ -764,8 +763,6 @@ PLDHashTable::Iterator::Iterator(Iterator&& aOther)
764763

765764
PLDHashTable::Iterator::Iterator(PLDHashTable* aTable)
766765
: mTable(aTable)
767-
, mLimit(mTable->mEntryStore.SlotForIndex(mTable->Capacity(), mTable->mEntrySize,
768-
mTable->Capacity()))
769766
, mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize,
770767
mTable->Capacity()))
771768
, mNexts(0)
@@ -787,10 +784,8 @@ PLDHashTable::Iterator::Iterator(PLDHashTable* aTable)
787784
}
788785

789786
// Advance to the first live entry, if there is one.
790-
if (!Done()) {
791-
while (IsOnNonLiveEntry()) {
792-
MoveToNextEntry();
793-
}
787+
if (!Done() && IsOnNonLiveEntry()) {
788+
MoveToNextLiveEntry();
794789
}
795790
}
796791

@@ -813,17 +808,6 @@ PLDHashTable::Iterator::IsOnNonLiveEntry() const
813808
return !mCurrent.IsLive();
814809
}
815810

816-
MOZ_ALWAYS_INLINE void
817-
PLDHashTable::Iterator::MoveToNextEntry()
818-
{
819-
mCurrent.Next(mEntrySize);
820-
if (mCurrent == mLimit) {
821-
// We wrapped around. Possible due to chaos mode.
822-
mCurrent = mTable->mEntryStore.SlotForIndex(0, mEntrySize,
823-
mTable->CapacityFromHashShift());
824-
}
825-
}
826-
827811
void
828812
PLDHashTable::Iterator::Next()
829813
{
@@ -833,12 +817,46 @@ PLDHashTable::Iterator::Next()
833817

834818
// Advance to the next live entry, if there is one.
835819
if (!Done()) {
836-
do {
837-
MoveToNextEntry();
838-
} while (IsOnNonLiveEntry());
820+
MoveToNextLiveEntry();
839821
}
840822
}
841823

824+
MOZ_ALWAYS_INLINE void
825+
PLDHashTable::Iterator::MoveToNextLiveEntry()
826+
{
827+
// Chaos mode requires wraparound to cover all possible entries, so we can't
828+
// simply move to the next live entry and stop when we hit the end of the
829+
// entry store. But we don't want to introduce extra branches into our inner
830+
// loop. So we are going to exploit the structure of the entry store in this
831+
// method to implement an efficient inner loop.
832+
//
833+
// The idea is that since we are really only iterating through the stored
834+
// hashes and because we know that there are a power-of-two number of
835+
// hashes, we can use masking to implement the wraparound for us. This
836+
// method does have the downside of needing to recalculate where the
837+
// associated entry is once we've found it, but that seems OK.
838+
839+
// Our current slot and its associated hash.
840+
Slot slot = mCurrent;
841+
PLDHashNumber* p = slot.HashPtr();
842+
const uint32_t capacity = mTable->CapacityFromHashShift();
843+
const uint32_t mask = capacity - 1;
844+
auto hashes = reinterpret_cast<PLDHashNumber*>(mTable->mEntryStore.Get());
845+
uint32_t slotIndex = p - hashes;
846+
847+
do {
848+
slotIndex = (slotIndex + 1) & mask;
849+
} while (!Slot::IsLiveHash(hashes[slotIndex]));
850+
851+
// slotIndex now indicates where a live slot is. Rematerialize the entry
852+
// and the slot.
853+
auto entries = reinterpret_cast<char*>(&hashes[capacity]);
854+
char* entryPtr = entries + slotIndex * mEntrySize;
855+
auto entry = reinterpret_cast<PLDHashEntryHdr*>(entryPtr);
856+
857+
mCurrent = Slot(entry, &hashes[slotIndex]);
858+
}
859+
842860
void
843861
PLDHashTable::Iterator::Remove()
844862
{

xpcom/ds/PLDHashTable.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ class PLDHashTable
247247

248248
bool IsFree() const { return KeyHash() == 0; }
249249
bool IsRemoved() const { return KeyHash() == 1; }
250-
bool IsLive() const { return KeyHash() >= 2; }
250+
bool IsLive() const { return IsLiveHash(KeyHash()); }
251+
static bool IsLiveHash(uint32_t aHash) { return aHash >= 2; }
251252

252253
void MarkFree() { *HashPtr() = 0; }
253254
void MarkRemoved() { *HashPtr() = 1; }
@@ -259,9 +260,9 @@ class PLDHashTable
259260
mEntry = reinterpret_cast<PLDHashEntryHdr*>(p);
260261
mKeyHash++;
261262
}
262-
private:
263263
PLDHashNumber* HashPtr() const { return mKeyHash; }
264264

265+
private:
265266
PLDHashEntryHdr* mEntry;
266267
PLDHashNumber* mKeyHash;
267268
};
@@ -613,7 +614,6 @@ class PLDHashTable
613614
PLDHashTable* mTable; // Main table pointer.
614615

615616
private:
616-
Slot mLimit; // One past the last entry.
617617
Slot mCurrent; // Pointer to the current entry.
618618
uint32_t mNexts; // Number of Next() calls.
619619
uint32_t mNextsLimit; // Next() call limit.
@@ -622,7 +622,8 @@ class PLDHashTable
622622
uint8_t mEntrySize; // Size of entries.
623623

624624
bool IsOnNonLiveEntry() const;
625-
void MoveToNextEntry();
625+
626+
void MoveToNextLiveEntry();
626627

627628
Iterator() = delete;
628629
Iterator(const Iterator&) = delete;

0 commit comments

Comments
 (0)