From 333a284ccff379175e4e9580345f99a231e61961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Nordheim?= Date: Thu, 9 Dec 2021 12:18:05 +0100 Subject: [PATCH] QHash: Move span constants to its own struct To avoid needing a templated Span to access them Task-number: QTBUG-98436 Change-Id: I5c3b16f9a806986b14ed794a3818990aa3ead0f7 Reviewed-by: Thiago Macieira Reviewed-by: Marc Mutz --- src/corelib/tools/qhash.h | 103 +++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index d9dfda71c1..a31265ed99 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -254,6 +254,14 @@ constexpr bool isRelocatable() return QTypeInfo::isRelocatable && QTypeInfo::isRelocatable; } +struct SpanConstants { + static constexpr size_t NEntries = 128; + static constexpr size_t LocalBucketMask = (NEntries - 1); + static constexpr size_t UnusedEntry = 0xff; + + static_assert ((NEntries & LocalBucketMask) == 0, "NEntries must be a power of two."); +}; + // Regular hash tables consist of a list of buckets that can store Nodes. But simply allocating one large array of buckets // would waste a lot of memory. To avoid this, we split the vector of buckets up into a vector of Spans. Each Span represents // NEntries buckets. To quickly find the correct Span that holds a bucket, NEntries must be a power of two. @@ -264,13 +272,6 @@ constexpr bool isRelocatable() // table have a very small memory overhead compared to many other implementations. template struct Span { - enum { - NEntries = 128, - LocalBucketMask = (NEntries - 1), - UnusedEntry = 0xff - }; - static_assert ((NEntries & LocalBucketMask) == 0, "EntriesPerSpan must be a power of two."); - // Entry is a slot available for storing a Node. The Span holds a pointer to // an array of Entries. Upon construction of the array, those entries are // unused, and nextFree() is being used to set up a singly linked list @@ -284,13 +285,13 @@ struct Span { Node &node() { return *reinterpret_cast(&storage); } }; - unsigned char offsets[NEntries]; + unsigned char offsets[SpanConstants::NEntries]; Entry *entries = nullptr; unsigned char allocated = 0; unsigned char nextFree = 0; Span() noexcept { - memset(offsets, UnusedEntry, sizeof(offsets)); + memset(offsets, SpanConstants::UnusedEntry, sizeof(offsets)); } ~Span() { @@ -301,7 +302,7 @@ struct Span { if (entries) { if constexpr (!std::is_trivially_destructible::value) { for (auto o : offsets) { - if (o != UnusedEntry) + if (o != SpanConstants::UnusedEntry) entries[o].node().~Node(); } } @@ -311,8 +312,8 @@ struct Span { } Node *insert(size_t i) { - Q_ASSERT(i <= NEntries); - Q_ASSERT(offsets[i] == UnusedEntry); + Q_ASSERT(i <= SpanConstants::NEntries); + Q_ASSERT(offsets[i] == SpanConstants::UnusedEntry); if (nextFree == allocated) addStorage(); unsigned char entry = nextFree; @@ -323,11 +324,11 @@ struct Span { } void erase(size_t bucket) noexcept(std::is_nothrow_destructible::value) { - Q_ASSERT(bucket <= NEntries); - Q_ASSERT(offsets[bucket] != UnusedEntry); + Q_ASSERT(bucket <= SpanConstants::NEntries); + Q_ASSERT(offsets[bucket] != SpanConstants::UnusedEntry); unsigned char entry = offsets[bucket]; - offsets[bucket] = UnusedEntry; + offsets[bucket] = SpanConstants::UnusedEntry; entries[entry].node().~Node(); entries[entry].nextFree() = nextFree; @@ -339,19 +340,19 @@ struct Span { } bool hasNode(size_t i) const noexcept { - return (offsets[i] != UnusedEntry); + return (offsets[i] != SpanConstants::UnusedEntry); } Node &at(size_t i) noexcept { - Q_ASSERT(i <= NEntries); - Q_ASSERT(offsets[i] != UnusedEntry); + Q_ASSERT(i <= SpanConstants::NEntries); + Q_ASSERT(offsets[i] != SpanConstants::UnusedEntry); return entries[offsets[i]].node(); } const Node &at(size_t i) const noexcept { - Q_ASSERT(i <= NEntries); - Q_ASSERT(offsets[i] != UnusedEntry); + Q_ASSERT(i <= SpanConstants::NEntries); + Q_ASSERT(offsets[i] != SpanConstants::UnusedEntry); return entries[offsets[i]].node(); } @@ -369,17 +370,17 @@ struct Span { } void moveLocal(size_t from, size_t to) noexcept { - Q_ASSERT(offsets[from] != UnusedEntry); - Q_ASSERT(offsets[to] == UnusedEntry); + Q_ASSERT(offsets[from] != SpanConstants::UnusedEntry); + Q_ASSERT(offsets[to] == SpanConstants::UnusedEntry); offsets[to] = offsets[from]; - offsets[from] = UnusedEntry; + offsets[from] = SpanConstants::UnusedEntry; } void moveFromSpan(Span &fromSpan, size_t fromIndex, size_t to) noexcept(std::is_nothrow_move_constructible_v) { - Q_ASSERT(to <= NEntries); - Q_ASSERT(offsets[to] == UnusedEntry); - Q_ASSERT(fromIndex <= NEntries); - Q_ASSERT(fromSpan.offsets[fromIndex] != UnusedEntry); + Q_ASSERT(to <= SpanConstants::NEntries); + Q_ASSERT(offsets[to] == SpanConstants::UnusedEntry); + Q_ASSERT(fromIndex <= SpanConstants::NEntries); + Q_ASSERT(fromSpan.offsets[fromIndex] != SpanConstants::UnusedEntry); if (nextFree == allocated) addStorage(); Q_ASSERT(nextFree < allocated); @@ -388,7 +389,7 @@ struct Span { nextFree = toEntry.nextFree(); size_t fromOffset = fromSpan.offsets[fromIndex]; - fromSpan.offsets[fromIndex] = UnusedEntry; + fromSpan.offsets[fromIndex] = SpanConstants::UnusedEntry; Entry &fromEntry = fromSpan.entries[fromOffset]; if constexpr (isRelocatable()) { @@ -403,14 +404,14 @@ struct Span { void addStorage() { - Q_ASSERT(allocated < NEntries); + Q_ASSERT(allocated < SpanConstants::NEntries); Q_ASSERT(nextFree == allocated); // the hash table should always be between 25 and 50% full // this implies that we on average have between 32 and 64 entries // in here. The likelihood of having below 16 entries is very small, // so start with that and increment by 16 each time we need to add // some more space - const size_t increment = NEntries / 8; + const size_t increment = SpanConstants::NEntries / 8; size_t alloc = allocated + increment; Entry *newEntries = new Entry[alloc]; // we only add storage if the previous storage was fully filled, so @@ -443,12 +444,10 @@ inline constexpr size_t maxNumBuckets() noexcept using Node3 = Node; static_assert(sizeof(Span) == sizeof(Span)); static_assert(sizeof(Span) == sizeof(Span)); - static_assert(int(Span::NEntries) == int(Span::NEntries)); - static_assert(int(Span::NEntries) == int(Span::NEntries)); // Maximum is 2^31-1 or 2^63-1 bytes (limited by qsizetype and ptrdiff_t) size_t max = (std::numeric_limits::max)(); - return max / sizeof(Span) * Span::NEntries; + return max / sizeof(Span) * SpanConstants::NEntries; } inline constexpr size_t bucketsForCapacity(size_t requestedCapacity) noexcept { @@ -486,7 +485,7 @@ struct Data Data(size_t reserve = 0) { numBuckets = GrowthPolicy::bucketsForCapacity(reserve); - size_t nSpans = (numBuckets + Span::LocalBucketMask) / Span::NEntries; + size_t nSpans = (numBuckets + SpanConstants::LocalBucketMask) / SpanConstants::NEntries; spans = new Span[nSpans]; seed = QHashSeed::globalSeed(); } @@ -498,17 +497,17 @@ struct Data if (reserved) numBuckets = GrowthPolicy::bucketsForCapacity(qMax(size, reserved)); bool resized = numBuckets != other.numBuckets; - size_t nSpans = (numBuckets + Span::LocalBucketMask) / Span::NEntries; + size_t nSpans = (numBuckets + SpanConstants::LocalBucketMask) / SpanConstants::NEntries; spans = new Span[nSpans]; - size_t otherNSpans = (other.numBuckets + Span::LocalBucketMask) / Span::NEntries; + size_t otherNSpans = (other.numBuckets + SpanConstants::LocalBucketMask) / SpanConstants::NEntries; for (size_t s = 0; s < otherNSpans; ++s) { const Span &span = other.spans[s]; - for (size_t index = 0; index < Span::NEntries; ++index) { + for (size_t index = 0; index < SpanConstants::NEntries; ++index) { if (!span.hasNode(index)) continue; const Node &n = span.at(index); - iterator it = resized ? find(n.key) : iterator{ this, s*Span::NEntries + index }; + iterator it = resized ? find(n.key) : iterator{ this, s*SpanConstants::NEntries + index }; Q_ASSERT(it.isUnused()); Node *newNode = spans[it.span()].insert(it.index()); new (newNode) Node(n); @@ -560,14 +559,14 @@ struct Data Span *oldSpans = spans; size_t oldBucketCount = numBuckets; - size_t nSpans = (newBucketCount + Span::LocalBucketMask) / Span::NEntries; + size_t nSpans = (newBucketCount + SpanConstants::LocalBucketMask) / SpanConstants::NEntries; spans = new Span[nSpans]; numBuckets = newBucketCount; - size_t oldNSpans = (oldBucketCount + Span::LocalBucketMask) / Span::NEntries; + size_t oldNSpans = (oldBucketCount + SpanConstants::LocalBucketMask) / SpanConstants::NEntries; for (size_t s = 0; s < oldNSpans; ++s) { Span &span = oldSpans[s]; - for (size_t index = 0; index < Span::NEntries; ++index) { + for (size_t index = 0; index < SpanConstants::NEntries; ++index) { if (!span.hasNode(index)) continue; Node &n = span.at(index); @@ -608,11 +607,11 @@ struct Data while (true) { // Split the bucket into the indexex of span array, and the local // offset inside the span - size_t span = bucket / Span::NEntries; - size_t index = bucket & Span::LocalBucketMask; + size_t span = bucket / SpanConstants::NEntries; + size_t index = bucket & SpanConstants::LocalBucketMask; Span &s = spans[span]; size_t offset = s.offset(index); - if (offset == Span::UnusedEntry) { + if (offset == SpanConstants::UnusedEntry) { return iterator{ this, bucket }; } else { Node &n = s.atOffset(offset); @@ -655,8 +654,8 @@ struct Data iterator erase(iterator it) noexcept(std::is_nothrow_destructible::value) { size_t bucket = it.bucket; - size_t span = bucket / Span::NEntries; - size_t index = bucket & Span::LocalBucketMask; + size_t span = bucket / SpanConstants::NEntries; + size_t index = bucket & SpanConstants::LocalBucketMask; Q_ASSERT(spans[span].hasNode(index)); spans[span].erase(index); --size; @@ -666,8 +665,8 @@ struct Data size_t next = bucket; while (true) { next = nextBucket(next); - size_t nextSpan = next / Span::NEntries; - size_t nextIndex = next & Span::LocalBucketMask; + size_t nextSpan = next / SpanConstants::NEntries; + size_t nextIndex = next & SpanConstants::LocalBucketMask; if (!spans[nextSpan].hasNode(nextIndex)) break; size_t hash = QHashPrivate::calculateHash(spans[nextSpan].at(nextIndex).key, seed); @@ -678,8 +677,8 @@ struct Data break; } else if (newBucket == hole) { // move into hole - size_t holeSpan = hole / Span::NEntries; - size_t holeIndex = hole & Span::LocalBucketMask; + size_t holeSpan = hole / SpanConstants::NEntries; + size_t holeIndex = hole & SpanConstants::LocalBucketMask; if (nextSpan == holeSpan) { spans[holeSpan].moveLocal(nextIndex, holeIndex); } else { @@ -712,8 +711,8 @@ struct iterator { const Data *d = nullptr; size_t bucket = 0; - size_t span() const noexcept { return bucket / Span::NEntries; } - size_t index() const noexcept { return bucket & Span::LocalBucketMask; } + size_t span() const noexcept { return bucket / SpanConstants::NEntries; } + size_t index() const noexcept { return bucket & SpanConstants::LocalBucketMask; } inline bool isUnused() const noexcept { return !d->spans[span()].hasNode(index()); } inline Node *node() const noexcept