From 340c0c2ab023f4a12217e57019fa852aafaa2e55 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Mon, 15 Mar 2021 08:39:51 +0100 Subject: [PATCH] QHash: Improve memory-allocation strategy Be smarter when allocating memory for the real data in addStorage(). As the amount of entries in there follow a binominal distribution, we know pretty well, how many there will be at least and at most. This avoids most of the reallocations of the storage when not rehashing, while Tessil's tests show that the total memory consumption has not changed. Task-number: QTBUG-91739 Task-number: QTBUG-98436 Change-Id: I98854bfbde8b2a16e788bfa1890c694d38fd09b5 Reviewed-by: Thiago Macieira --- src/corelib/tools/qhash.h | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index 23102773ea..49a015f2fe 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -408,11 +408,24 @@ struct Span { 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 = SpanConstants::NEntries / 8; - size_t alloc = allocated + increment; + // in here. More exactly, we have a binominal distribution of the amount of + // occupied entries. + // For a 25% filled table, the average is 32 entries, with a 95% chance that we have between + // 23 and 41 entries. + // For a 50% filled table, the average is 64 entries, with a 95% chance that we have between + // 53 and 75 entries. + // Since we only resize the table once it's 50% filled and we want to avoid copies of + // data where possible, we initially allocate 48 entries, then resize to 80 entries, after that + // resize by increments of 16. That way, we usually only get one resize of the table + // while filling it. + size_t alloc; + static_assert(SpanConstants::NEntries % 8 == 0); + if (!allocated) + alloc = SpanConstants::NEntries / 8 * 3; + else if (allocated == SpanConstants::NEntries / 8 * 3) + alloc = SpanConstants::NEntries / 8 * 5; + else + alloc = allocated + SpanConstants::NEntries/8; Entry *newEntries = new Entry[alloc]; // we only add storage if the previous storage was fully filled, so // simply copy the old data over @@ -425,7 +438,7 @@ struct Span { entries[i].node().~Node(); } } - for (size_t i = allocated; i < allocated + increment; ++i) { + for (size_t i = allocated; i < alloc; ++i) { newEntries[i].nextFree() = uchar(i + 1); } delete[] entries;