Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 5cad7a5

Browse files
benaadamsstephentoub
authored andcommitted
Improve Dictionary TryInsert CQ
Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
1 parent c340cd3 commit 5cad7a5

File tree

1 file changed

+49
-22
lines changed

1 file changed

+49
-22
lines changed

src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -410,17 +410,27 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
410410
}
411411

412412
if (_buckets == null) Initialize(0);
413-
int hashCode = _comparer.GetHashCode(key) & 0x7FFFFFFF;
414-
int targetBucket = hashCode % _buckets.Length;
413+
IEqualityComparer<TKey> comparer = _comparer;
414+
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
415415
int collisionCount = 0;
416416

417-
for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next)
417+
ref int bucket = ref _buckets[hashCode % _buckets.Length];
418+
int i = bucket;
419+
Entry[] entries = _entries;
420+
do
418421
{
419-
if (_entries[i].hashCode == hashCode && _comparer.Equals(_entries[i].key, key))
422+
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
423+
// Test uint in if rather than loop condition to drop range check for following array access
424+
if ((uint)i >= (uint)entries.Length)
425+
{
426+
break;
427+
}
428+
429+
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
420430
{
421431
if (behavior == InsertionBehavior.OverwriteExisting)
422432
{
423-
_entries[i].value = value;
433+
entries[i].value = value;
424434
_version++;
425435
return true;
426436
}
@@ -432,41 +442,56 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
432442

433443
return false;
434444
}
445+
446+
i = entries[i].next;
435447
collisionCount++;
436-
}
448+
} while (true);
437449

450+
// Can be improved with "Ref Local Reassignment"
451+
// https://github.com/dotnet/csharplang/blob/master/proposals/ref-local-reassignment.md
452+
bool resized = false;
453+
bool updateFreeList = false;
438454
int index;
439455
if (_freeCount > 0)
440456
{
441457
index = _freeList;
442-
_freeList = _entries[index].next;
458+
updateFreeList = true;
443459
_freeCount--;
444460
}
445461
else
446462
{
447-
if (_count == _entries.Length)
463+
int count = _count;
464+
if (count == entries.Length)
448465
{
449466
Resize();
450-
targetBucket = hashCode % _buckets.Length;
467+
resized = true;
451468
}
452-
index = _count;
453-
_count++;
469+
index = count;
470+
_count = count + 1;
471+
entries = _entries;
454472
}
455473

456-
_entries[index].hashCode = hashCode;
457-
_entries[index].next = _buckets[targetBucket];
458-
_entries[index].key = key;
459-
_entries[index].value = value;
460-
_buckets[targetBucket] = index;
461-
_version++;
474+
ref int targetBucket = ref resized ? ref _buckets[hashCode % _buckets.Length] : ref bucket;
475+
ref Entry entry = ref entries[index];
462476

463-
// If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
464-
// i.e. EqualityComparer<string>.Default.
477+
if (updateFreeList)
478+
{
479+
_freeList = entry.next;
480+
}
481+
entry.hashCode = hashCode;
482+
entry.next = targetBucket;
483+
entry.key = key;
484+
entry.value = value;
485+
targetBucket = index;
486+
_version++;
465487

466-
if (collisionCount > HashHelpers.HashCollisionThreshold && _comparer is NonRandomizedStringEqualityComparer)
488+
// Value types never rehash
489+
if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && _comparer is NonRandomizedStringEqualityComparer)
467490
{
491+
// If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
492+
// i.e. EqualityComparer<string>.Default.
468493
_comparer = (IEqualityComparer<TKey>)EqualityComparer<string>.Default;
469-
Resize(_entries.Length, true);
494+
Resize(entries.Length, true);
470495
}
471496

472497
return true;
@@ -525,6 +550,8 @@ private void Resize()
525550

526551
private void Resize(int newSize, bool forceNewHashCodes)
527552
{
553+
// Value types never rehash
554+
Debug.Assert(!forceNewHashCodes || default(TKey) == null);
528555
Debug.Assert(newSize >= _entries.Length);
529556

530557
int[] buckets = new int[newSize];
@@ -537,7 +564,7 @@ private void Resize(int newSize, bool forceNewHashCodes)
537564
int count = _count;
538565
Array.Copy(_entries, 0, entries, 0, count);
539566

540-
if (forceNewHashCodes)
567+
if (default(TKey) == null && forceNewHashCodes)
541568
{
542569
for (int i = 0; i < count; i++)
543570
{

0 commit comments

Comments
 (0)