Skip to content

Commit 529e924

Browse files
committed
Bug 1460674 - part 2 - reorganize PLDHashTable's internal storage; r=njn
As discussed in the previous commit message, PLDHashTable's storage wastes space for common entry types. This commit reorganizes the storage to store all the hashes packed together, followed by all the entries, which eliminates said waste.
1 parent d848c8a commit 529e924

File tree

2 files changed

+118
-93
lines changed

2 files changed

+118
-93
lines changed

xpcom/ds/PLDHashTable.cpp

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ PLDHashTable::StubOps()
127127
static bool
128128
SizeOfEntryStore(uint32_t aCapacity, uint32_t aEntrySize, uint32_t* aNbytes)
129129
{
130-
uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(aEntrySize);
131-
*aNbytes = aCapacity * aEntrySize;
130+
uint32_t slotSize = aEntrySize + sizeof(PLDHashNumber);
131+
uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(slotSize);
132+
*aNbytes = aCapacity * slotSize;
132133
return uint64_t(*aNbytes) == nbytes64; // returns false on overflow
133134
}
134135

@@ -306,24 +307,11 @@ PLDHashTable::MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aKeyHash)
306307
return (aSlot.KeyHash() & ~kCollisionFlag) == aKeyHash;
307308
}
308309

309-
/* static */ bool
310-
PLDHashTable::MatchEntryKeyhash(const PLDHashEntryHdr* aEntry,
311-
const PLDHashNumber aKeyHash)
312-
{
313-
return (aEntry->mKeyHash & ~kCollisionFlag) == aKeyHash;
314-
}
315-
316310
// Compute the address of the indexed entry in table.
317311
auto
318312
PLDHashTable::SlotForIndex(uint32_t aIndex) const -> Slot
319313
{
320-
return mEntryStore.SlotForIndex(aIndex, mEntrySize);
321-
}
322-
323-
PLDHashEntryHdr*
324-
PLDHashTable::AddressEntry(uint32_t aIndex) const
325-
{
326-
return SlotForIndex(aIndex).ToEntry();
314+
return mEntryStore.SlotForIndex(aIndex, mEntrySize, CapacityFromHashShift());
327315
}
328316

329317
PLDHashTable::~PLDHashTable()
@@ -617,7 +605,7 @@ PLDHashTable::Add(const void* aKey, const mozilla::fallible_t&)
617605
[&](Slot& found) -> Slot { return found; },
618606
[&]() -> Slot {
619607
MOZ_CRASH("Nope");
620-
return Slot(nullptr);
608+
return Slot(nullptr, nullptr);
621609
});
622610
if (!slot.IsLive()) {
623611
// Initialize the slot, indicating that it's no longer free.
@@ -692,7 +680,7 @@ PLDHashTable::RemoveEntry(PLDHashEntryHdr* aEntry)
692680
void
693681
PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry)
694682
{
695-
Slot slot(aEntry);
683+
Slot slot(mEntryStore.SlotForPLDHashEntry(aEntry, Capacity(), mEntrySize));
696684
RawRemove(slot);
697685
}
698686

@@ -776,8 +764,10 @@ PLDHashTable::Iterator::Iterator(Iterator&& aOther)
776764

777765
PLDHashTable::Iterator::Iterator(PLDHashTable* aTable)
778766
: mTable(aTable)
779-
, mLimit(mTable->mEntryStore.SlotForIndex(mTable->Capacity(), mTable->mEntrySize))
780-
, mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize))
767+
, mLimit(mTable->mEntryStore.SlotForIndex(mTable->Capacity(), mTable->mEntrySize,
768+
mTable->Capacity()))
769+
, mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize,
770+
mTable->Capacity()))
781771
, mNexts(0)
782772
, mNextsLimit(mTable->EntryCount())
783773
, mHaveRemoved(false)
@@ -791,8 +781,9 @@ PLDHashTable::Iterator::Iterator(PLDHashTable* aTable)
791781
mTable->Capacity() > 0) {
792782
// Start iterating at a random entry. It would be even more chaotic to
793783
// iterate in fully random order, but that's harder.
794-
uint32_t i = ChaosMode::randomUint32LessThan(mTable->Capacity());
795-
mCurrent = mTable->mEntryStore.SlotForIndex(i, mTable->mEntrySize);
784+
uint32_t capacity = mTable->CapacityFromHashShift();
785+
uint32_t i = ChaosMode::randomUint32LessThan(capacity);
786+
mCurrent = mTable->mEntryStore.SlotForIndex(i, mTable->mEntrySize, capacity);
796787
}
797788

798789
// Advance to the first live entry, if there is one.
@@ -828,7 +819,8 @@ PLDHashTable::Iterator::MoveToNextEntry()
828819
mCurrent.Next(mEntrySize);
829820
if (mCurrent == mLimit) {
830821
// We wrapped around. Possible due to chaos mode.
831-
mCurrent = mTable->mEntryStore.SlotForIndex(0, mEntrySize);
822+
mCurrent = mTable->mEntryStore.SlotForIndex(0, mEntrySize,
823+
mTable->CapacityFromHashShift());
832824
}
833825
}
834826

xpcom/ds/PLDHashTable.h

Lines changed: 103 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@ struct PLDHashTableOps;
3737
// structure, for single static initialization per hash table sub-type.
3838
//
3939
// Each hash table sub-type should make its entry type a subclass of
40-
// PLDHashEntryHdr. The mKeyHash member contains the result of suitably
41-
// scrambling the hash code returned from the hashKey callback (see below),
42-
// then constraining the result to avoid the magic 0 and 1 values. The stored
43-
// mKeyHash value is table size invariant, and it is maintained automatically
44-
// -- users need never access it.
40+
// PLDHashEntryHdr. PLDHashEntryHdr is merely a common superclass to present a
41+
// uniform interface to PLDHashTable clients. The zero-sized base class
42+
// optimization, employed by all of our supported C++ compilers, will ensure
43+
// that this abstraction does not make objects needlessly larger.
4544
struct PLDHashEntryHdr
4645
{
4746
PLDHashEntryHdr() = default;
@@ -52,8 +51,6 @@ struct PLDHashEntryHdr
5251

5352
private:
5453
friend class PLDHashTable;
55-
56-
PLDHashNumber mKeyHash;
5754
};
5855

5956
#ifdef DEBUG
@@ -223,57 +220,98 @@ class Checker
223220
class PLDHashTable
224221
{
225222
private:
226-
// A slot represents a cached hash value and its associated entry stored
227-
// in the hash table. While they currently belong to the same object,
228-
// PLDHashEntryHdr, they do not necessarily need to be contiguous in memory,
229-
// and this abstraction helps enforce the separation between the two.
223+
// A slot represents a cached hash value and its associated entry stored in
224+
// the hash table. The hash value and the entry are not stored contiguously.
230225
struct Slot
231226
{
232-
Slot(PLDHashEntryHdr* aEntry)
227+
Slot(PLDHashEntryHdr* aEntry, PLDHashNumber* aKeyHash)
233228
: mEntry(aEntry)
229+
, mKeyHash(aKeyHash)
234230
{}
235231

236232
Slot(const Slot&) = default;
237-
Slot(Slot&& aOther)
238-
: mEntry(aOther.mEntry)
239-
{}
233+
Slot(Slot&& aOther) = default;
240234

241235
Slot& operator=(Slot&& aOther) {
242236
this->~Slot();
243237
new (this) Slot(std::move(aOther));
244238
return *this;
245239
}
246240

247-
bool operator==(const Slot& aOther)
248-
{
249-
return mEntry == aOther.mEntry;
250-
}
241+
bool operator==(const Slot& aOther) { return mEntry == aOther.mEntry; }
251242

252-
PLDHashNumber KeyHash() const { return mEntry->mKeyHash; }
253-
void SetKeyHash(PLDHashNumber aHash) { mEntry->mKeyHash = aHash; }
243+
PLDHashNumber KeyHash() const { return *HashPtr(); }
244+
void SetKeyHash(PLDHashNumber aHash) { *HashPtr() = aHash; }
254245

255246
PLDHashEntryHdr* ToEntry() const { return mEntry; }
256247

257248
bool IsFree() const { return KeyHash() == 0; }
258249
bool IsRemoved() const { return KeyHash() == 1; }
259250
bool IsLive() const { return KeyHash() >= 2; }
260251

261-
void MarkFree() { mEntry->mKeyHash = 0; }
262-
void MarkRemoved() { mEntry->mKeyHash = 1; }
263-
void MarkColliding() { mEntry->mKeyHash |= kCollisionFlag; }
252+
void MarkFree() { *HashPtr() = 0; }
253+
void MarkRemoved() { *HashPtr() = 1; }
254+
void MarkColliding() { *HashPtr() |= kCollisionFlag; }
264255

265256
void Next(uint32_t aEntrySize) {
266257
char* p = reinterpret_cast<char*>(mEntry);
267258
p += aEntrySize;
268259
mEntry = reinterpret_cast<PLDHashEntryHdr*>(p);
260+
mKeyHash++;
269261
}
270262
private:
263+
PLDHashNumber* HashPtr() const { return mKeyHash; }
264+
271265
PLDHashEntryHdr* mEntry;
266+
PLDHashNumber* mKeyHash;
272267
};
273268

274269
// This class maintains the invariant that every time the entry store is
275270
// changed, the generation is updated.
276271
//
272+
// The data layout separates the cached hashes of entries and the entries
273+
// themselves to save space. We could store the entries thusly:
274+
//
275+
// +--------+--------+---------+
276+
// | entry0 | entry1 | ... |
277+
// +--------+--------+---------+
278+
//
279+
// where the entries themselves contain the cached hash stored as their
280+
// first member. PLDHashTable did this for a long time, with entries looking
281+
// like:
282+
//
283+
// class PLDHashEntryHdr
284+
// {
285+
// PLDHashNumber mKeyHash;
286+
// };
287+
//
288+
// class MyEntry : public PLDHashEntryHdr
289+
// {
290+
// ...
291+
// };
292+
//
293+
// The problem with this setup is that, depending on the layout of
294+
// `MyEntry`, there may be platform ABI-mandated padding between `mKeyHash`
295+
// and the first member of `MyEntry`. This ABI-mandated padding is wasted
296+
// space, and was surprisingly common, e.g. when MyEntry contained a single
297+
// pointer on 64-bit platforms.
298+
//
299+
// As previously alluded to, the current setup stores things thusly:
300+
//
301+
// +-------+-------+-------+-------+--------+--------+---------+
302+
// | hash0 | hash1 | ..... | hashN | entry0 | entry1 | ... |
303+
// +-------+-------+-------+-------+--------+--------+---------+
304+
//
305+
// which contains no wasted space between the hashes themselves, and no
306+
// wasted space between the entries themselves. malloc is guaranteed to
307+
// return blocks of memory with at least word alignment on all of our major
308+
// platforms. PLDHashTable mandates that the size of the hash table is
309+
// always a power of two, so the alignment of the memory containing the
310+
// first entry is always at least the alignment of the entire entry store.
311+
// That means the alignment of `entry0` should be its natural alignment.
312+
// Entries may have problems if they contain over-aligned members such as
313+
// SIMD vector types, but this has not been a problem in practice.
314+
//
277315
// Note: It would be natural to store the generation within this class, but
278316
// we can't do that without bloating sizeof(PLDHashTable) on 64-bit machines.
279317
// So instead we store it outside this class, and Set() takes a pointer to it
@@ -283,9 +321,16 @@ class PLDHashTable
283321
private:
284322
char* mEntryStore;
285323

286-
PLDHashEntryHdr* EntryAt(uint32_t aIndex, uint32_t aEntrySize) const {
287-
return reinterpret_cast<PLDHashEntryHdr*>(Get() + aIndex * aEntrySize);
324+
static char* Entries(char* aStore, uint32_t aCapacity)
325+
{
326+
return aStore + aCapacity * sizeof(PLDHashNumber);
327+
}
328+
329+
char* Entries(uint32_t aCapacity) const
330+
{
331+
return Entries(Get(), aCapacity);
288332
}
333+
289334
public:
290335
EntryStore() : mEntryStore(nullptr) {}
291336

@@ -296,8 +341,24 @@ class PLDHashTable
296341
}
297342

298343
char* Get() const { return mEntryStore; }
299-
Slot SlotForIndex(uint32_t aIndex, uint32_t aEntrySize) const {
300-
return Slot(EntryAt(aIndex, aEntrySize));
344+
345+
Slot SlotForIndex(uint32_t aIndex, uint32_t aEntrySize,
346+
uint32_t aCapacity) const
347+
{
348+
char* entries = Entries(aCapacity);
349+
auto entry = reinterpret_cast<PLDHashEntryHdr*>(entries + aIndex * aEntrySize);
350+
auto hashes = reinterpret_cast<PLDHashNumber*>(Get());
351+
return Slot(entry, &hashes[aIndex]);
352+
}
353+
354+
Slot SlotForPLDHashEntry(PLDHashEntryHdr* aEntry,
355+
uint32_t aCapacity, uint32_t aEntrySize)
356+
{
357+
char* entries = Entries(aCapacity);
358+
char* entry = reinterpret_cast<char*>(aEntry);
359+
uint32_t entryOffset = entry - entries;
360+
uint32_t slotIndex = entryOffset / aEntrySize;
361+
return SlotForIndex(slotIndex, aEntrySize, aCapacity);
301362
}
302363

303364
template<typename F>
@@ -308,7 +369,9 @@ class PLDHashTable
308369
template<typename F>
309370
static void ForEachSlot(char* aStore, uint32_t aCapacity, uint32_t aEntrySize,
310371
F&& aFunc) {
311-
Slot slot(reinterpret_cast<PLDHashEntryHdr*>(aStore));
372+
char* entries = Entries(aStore, aCapacity);
373+
Slot slot(reinterpret_cast<PLDHashEntryHdr*>(entries),
374+
reinterpret_cast<PLDHashNumber*>(aStore));
312375
for (size_t i = 0; i < aCapacity; ++i) {
313376
aFunc(slot);
314377
slot.Next(aEntrySize);
@@ -417,11 +480,9 @@ class PLDHashTable
417480
// If |entry| is null upon return, then the table is severely overloaded and
418481
// memory can't be allocated for entry storage.
419482
//
420-
// Otherwise, |aEntry->mKeyHash| has been set so that
421-
// PLDHashTable::EntryIsFree(entry) is false, and it is up to the caller to
422-
// initialize the key and value parts of the entry sub-type, if they have not
423-
// been set already (i.e. if entry was not already in the table, and if the
424-
// optional initEntry hook was not used).
483+
// Otherwise, if the initEntry hook was provided, |entry| will be
484+
// initialized. If the initEntry hook was not provided, the caller
485+
// should initialize |entry| as appropriate.
425486
PLDHashEntryHdr* Add(const void* aKey, const mozilla::fallible_t&);
426487

427488
// This is like the other Add() function, but infallible, and so never
@@ -583,37 +644,12 @@ class PLDHashTable
583644

584645
static const PLDHashNumber kCollisionFlag = 1;
585646

586-
static bool EntryIsFree(const PLDHashEntryHdr* aEntry)
587-
{
588-
return aEntry->mKeyHash == 0;
589-
}
590-
static bool EntryIsRemoved(const PLDHashEntryHdr* aEntry)
591-
{
592-
return aEntry->mKeyHash == 1;
593-
}
594-
static bool EntryIsLive(const PLDHashEntryHdr* aEntry)
595-
{
596-
return aEntry->mKeyHash >= 2;
597-
}
598-
599-
static void MarkEntryFree(PLDHashEntryHdr* aEntry)
600-
{
601-
aEntry->mKeyHash = 0;
602-
}
603-
static void MarkEntryRemoved(PLDHashEntryHdr* aEntry)
604-
{
605-
aEntry->mKeyHash = 1;
606-
}
607-
608647
PLDHashNumber Hash1(PLDHashNumber aHash0) const;
609648
void Hash2(PLDHashNumber aHash,
610649
uint32_t& aHash2Out, uint32_t& aSizeMaskOut) const;
611650

612651
static bool MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aHash);
613-
static bool MatchEntryKeyhash(const PLDHashEntryHdr* aEntry,
614-
const PLDHashNumber aHash);
615652
Slot SlotForIndex(uint32_t aIndex) const;
616-
PLDHashEntryHdr* AddressEntry(uint32_t aIndex) const;
617653

618654
// We store mHashShift rather than sizeLog2 to optimize the collision-free
619655
// case in SearchTable.
@@ -666,10 +702,8 @@ typedef void (*PLDHashMoveEntry)(PLDHashTable* aTable,
666702
typedef void (*PLDHashClearEntry)(PLDHashTable* aTable,
667703
PLDHashEntryHdr* aEntry);
668704

669-
// Initialize a new entry, apart from mKeyHash. This function is called when
670-
// Add() finds no existing entry for the given key, and must add a new one. At
671-
// that point, |aEntry->mKeyHash| is not set yet, to avoid claiming the last
672-
// free entry in a severely overloaded table.
705+
// Initialize a new entry. This function is called when
706+
// Add() finds no existing entry for the given key, and must add a new one.
673707
typedef void (*PLDHashInitEntry)(PLDHashEntryHdr* aEntry, const void* aKey);
674708

675709
// Finally, the "vtable" structure for PLDHashTable. The first four hooks
@@ -683,13 +717,12 @@ typedef void (*PLDHashInitEntry)(PLDHashEntryHdr* aEntry, const void* aKey);
683717
// clearEntry Run dtor on entry.
684718
//
685719
// Note the reason why initEntry is optional: the default hooks (stubs) clear
686-
// entry storage: On successful Add(tbl, key), the returned entry pointer
687-
// addresses an entry struct whose mKeyHash member has been set non-zero, but
688-
// all other entry members are still clear (null). Add() callers can test such
689-
// members to see whether the entry was newly created by the Add() call that
690-
// just succeeded. If placement new or similar initialization is required,
691-
// define an |initEntry| hook. Of course, the |clearEntry| hook must zero or
692-
// null appropriately.
720+
// entry storage. On a successful Add(tbl, key), the returned entry pointer
721+
// addresses an entry struct whose entry members are still clear (null). Add()
722+
// callers can test such members to see whether the entry was newly created by
723+
// the Add() call that just succeeded. If placement new or similar
724+
// initialization is required, define an |initEntry| hook. Of course, the
725+
// |clearEntry| hook must zero or null appropriately.
693726
//
694727
// XXX assumes 0 is null for pointer types.
695728
struct PLDHashTableOps

0 commit comments

Comments
 (0)