LibGC: Amortize pruning of dead entries in WeakHashSet

Previously, every set() and remove() call would do a full O(capacity)
scan of the underlying hash table to remove dead weak references.
This caused significant stalls on pages where the set grew large, as
every insertion paid the full cost of scanning the entire table.

Fix this by tracking mutations since last prune and only performing
the scan after enough operations to amortize the cost. The threshold
scales with the table size (minimum 64), so the per-operation cost
is O(1) amortized.
This commit is contained in:
Andreas Kling 2026-03-08 16:57:37 +01:00 committed by Andreas Kling
parent 12af95cfa2
commit fc675e8634

View file

@ -27,13 +27,13 @@ public:
void set(T& value)
{
prune_dead_entries();
maybe_prune();
m_table.set(Weak<T>(value));
}
bool remove(T& value)
{
prune_dead_entries();
maybe_prune();
auto it = m_table.find(Traits<T*>::hash(&value), [&](auto& entry) { return entry.ptr() == &value; });
if (it == m_table.end())
return false;
@ -139,14 +139,18 @@ public:
ConstIterator end() const { return ConstIterator(m_table.end(), m_table.end()); }
private:
void prune_dead_entries()
void maybe_prune()
{
if (++m_mutations_since_last_prune < max(m_table.size(), static_cast<size_t>(64)))
return;
m_table.remove_all_matching([](Weak<T> const& entry) {
return !entry.ptr();
});
m_mutations_since_last_prune = 0;
}
TableType m_table;
size_t m_mutations_since_last_prune { 0 };
};
}