Skip to content

Commit

Permalink
Avoid taking lock for empty bucket in ConcurrentDictionary.TryRemove (#…
Browse files Browse the repository at this point in the history
…82004)

Even when uncontended, the lock represents the most expensive work performed by the method.  We can avoid taking it if we see that the bucket's count is empty.
  • Loading branch information
stephentoub authored Feb 12, 2023
1 parent 4f3308f commit 252018c
Showing 1 changed file with 39 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -390,52 +390,57 @@ private bool TryRemoveInternal(TKey key, [MaybeNullWhen(false)] out TValue value
object[] locks = tables._locks;
ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo);

lock (locks[lockNo])
// Do a hot read on number of items stored in the bucket. If it's empty, we can avoid
// taking the lock and fail fast.
if (tables._countPerLock[lockNo] != 0)
{
// If the table just got resized, we may not be holding the right lock, and must retry.
// This should be a rare occurrence.
if (tables != _tables)
lock (locks[lockNo])
{
tables = _tables;
if (!ReferenceEquals(comparer, tables._comparer))
// If the table just got resized, we may not be holding the right lock, and must retry.
// This should be a rare occurrence.
if (tables != _tables)
{
comparer = tables._comparer;
hashcode = GetHashCode(comparer, key);
tables = _tables;
if (!ReferenceEquals(comparer, tables._comparer))
{
comparer = tables._comparer;
hashcode = GetHashCode(comparer, key);
}
continue;
}
continue;
}

Node? prev = null;
for (Node? curr = bucket; curr is not null; curr = curr._next)
{
Debug.Assert((prev is null && curr == bucket) || prev!._next == curr);

if (hashcode == curr._hashcode && NodeEqualsKey(comparer, curr, key))
Node? prev = null;
for (Node? curr = bucket; curr is not null; curr = curr._next)
{
if (matchValue)
Debug.Assert((prev is null && curr == bucket) || prev!._next == curr);

if (hashcode == curr._hashcode && NodeEqualsKey(comparer, curr, key))
{
bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr._value);
if (!valuesMatch)
if (matchValue)
{
value = default;
return false;
bool valuesMatch = EqualityComparer<TValue>.Default.Equals(oldValue, curr._value);
if (!valuesMatch)
{
value = default;
return false;
}
}
}

if (prev is null)
{
Volatile.Write(ref bucket, curr._next);
}
else
{
prev._next = curr._next;
}
if (prev is null)
{
Volatile.Write(ref bucket, curr._next);
}
else
{
prev._next = curr._next;
}

value = curr._value;
tables._countPerLock[lockNo]--;
return true;
value = curr._value;
tables._countPerLock[lockNo]--;
return true;
}
prev = curr;
}
prev = curr;
}
}

Expand Down

0 comments on commit 252018c

Please sign in to comment.