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

Commit f427718

Browse files
benaadamsdanmoseley
authored andcommitted
Dict cache default comparer for object types (#17285)
* Dict cache default comparer for object types * Improves * Fix EventRegistrationToken Equals
1 parent 2250e2a commit f427718

File tree

2 files changed

+143
-43
lines changed

2 files changed

+143
-43
lines changed

src/mscorlib/shared/System/Collections/Generic/Dictionary.cs

Lines changed: 132 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -275,18 +275,34 @@ public bool ContainsKey(TKey key)
275275

276276
public bool ContainsValue(TValue value)
277277
{
278+
Entry[] entries = _entries;
278279
if (value == null)
279280
{
280281
for (int i = 0; i < _count; i++)
281282
{
282-
if (_entries[i].hashCode >= 0 && _entries[i].value == null) return true;
283+
if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
283284
}
284285
}
285286
else
286287
{
287-
for (int i = 0; i < _count; i++)
288+
if (default(TValue) != null)
288289
{
289-
if (_entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(_entries[i].value, value)) return true;
290+
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
291+
for (int i = 0; i < _count; i++)
292+
{
293+
if (entries[i].hashCode >= 0 && EqualityComparer<TValue>.Default.Equals(entries[i].value, value)) return true;
294+
}
295+
}
296+
else
297+
{
298+
// Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
299+
// https://github.com/dotnet/coreclr/issues/17273
300+
// So cache in a local rather than get EqualityComparer per loop iteration
301+
EqualityComparer<TValue> defaultComparer = EqualityComparer<TValue>.Default;
302+
for (int i = 0; i < _count; i++)
303+
{
304+
if (entries[i].hashCode >= 0 && defaultComparer.Equals(entries[i].value, value)) return true;
305+
}
290306
}
291307
}
292308
return false;
@@ -364,24 +380,53 @@ private int FindEntry(TKey key)
364380
int hashCode = key.GetHashCode() & 0x7FFFFFFF;
365381
// Value in _buckets is 1-based
366382
i = buckets[hashCode % buckets.Length] - 1;
367-
do
383+
if (default(TKey) != null)
368384
{
369-
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
370-
// Test in if to drop range check for following array access
371-
if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
385+
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
386+
do
372387
{
373-
break;
374-
}
375-
376-
i = entries[i].next;
377-
if (collisionCount >= entries.Length)
388+
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
389+
// Test in if to drop range check for following array access
390+
if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key)))
391+
{
392+
break;
393+
}
394+
395+
i = entries[i].next;
396+
if (collisionCount >= entries.Length)
397+
{
398+
// The chain of entries forms a loop; which means a concurrent update has happened.
399+
// Break out of the loop and throw, rather than looping forever.
400+
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
401+
}
402+
collisionCount++;
403+
} while (true);
404+
}
405+
else
406+
{
407+
// Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
408+
// https://github.com/dotnet/coreclr/issues/17273
409+
// So cache in a local rather than get EqualityComparer per loop iteration
410+
EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
411+
do
378412
{
379-
// The chain of entries forms a loop; which means a concurrent update has happened.
380-
// Break out of the loop and throw, rather than looping forever.
381-
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
382-
}
383-
collisionCount++;
384-
} while (true);
413+
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
414+
// Test in if to drop range check for following array access
415+
if ((uint)i >= (uint)entries.Length || (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key)))
416+
{
417+
break;
418+
}
419+
420+
i = entries[i].next;
421+
if (collisionCount >= entries.Length)
422+
{
423+
// The chain of entries forms a loop; which means a concurrent update has happened.
424+
// Break out of the loop and throw, rather than looping forever.
425+
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
426+
}
427+
collisionCount++;
428+
} while (true);
429+
}
385430
}
386431
else
387432
{
@@ -449,40 +494,85 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
449494

450495
if (comparer == null)
451496
{
452-
do
497+
if (default(TKey) != null)
453498
{
454-
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
455-
// Test uint in if rather than loop condition to drop range check for following array access
456-
if ((uint)i >= (uint)entries.Length)
499+
// ValueType: Devirtualize with EqualityComparer<TValue>.Default intrinsic
500+
do
457501
{
458-
break;
459-
}
502+
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
503+
// Test uint in if rather than loop condition to drop range check for following array access
504+
if ((uint)i >= (uint)entries.Length)
505+
{
506+
break;
507+
}
460508

461-
if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
462-
{
463-
if (behavior == InsertionBehavior.OverwriteExisting)
509+
if (entries[i].hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entries[i].key, key))
464510
{
465-
entries[i].value = value;
466-
return true;
511+
if (behavior == InsertionBehavior.OverwriteExisting)
512+
{
513+
entries[i].value = value;
514+
return true;
515+
}
516+
517+
if (behavior == InsertionBehavior.ThrowOnExisting)
518+
{
519+
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
520+
}
521+
522+
return false;
467523
}
468524

469-
if (behavior == InsertionBehavior.ThrowOnExisting)
525+
i = entries[i].next;
526+
if (collisionCount >= entries.Length)
470527
{
471-
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
528+
// The chain of entries forms a loop; which means a concurrent update has happened.
529+
// Break out of the loop and throw, rather than looping forever.
530+
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
531+
}
532+
collisionCount++;
533+
} while (true);
534+
}
535+
else
536+
{
537+
// Object type: Shared Generic, EqualityComparer<TValue>.Default won't devirtualize
538+
// https://github.com/dotnet/coreclr/issues/17273
539+
// So cache in a local rather than get EqualityComparer per loop iteration
540+
EqualityComparer<TKey> defaultComparer = EqualityComparer<TKey>.Default;
541+
do
542+
{
543+
// Should be a while loop https://github.com/dotnet/coreclr/issues/15476
544+
// Test uint in if rather than loop condition to drop range check for following array access
545+
if ((uint)i >= (uint)entries.Length)
546+
{
547+
break;
472548
}
473549

474-
return false;
475-
}
550+
if (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key))
551+
{
552+
if (behavior == InsertionBehavior.OverwriteExisting)
553+
{
554+
entries[i].value = value;
555+
return true;
556+
}
557+
558+
if (behavior == InsertionBehavior.ThrowOnExisting)
559+
{
560+
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
561+
}
562+
563+
return false;
564+
}
476565

477-
i = entries[i].next;
478-
if (collisionCount >= entries.Length)
479-
{
480-
// The chain of entries forms a loop; which means a concurrent update has happened.
481-
// Break out of the loop and throw, rather than looping forever.
482-
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
483-
}
484-
collisionCount++;
485-
} while (true);
566+
i = entries[i].next;
567+
if (collisionCount >= entries.Length)
568+
{
569+
// The chain of entries forms a loop; which means a concurrent update has happened.
570+
// Break out of the loop and throw, rather than looping forever.
571+
ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
572+
}
573+
collisionCount++;
574+
} while (true);
575+
}
486576
}
487577
else
488578
{

src/mscorlib/src/System/Runtime/InteropServices/WindowsRuntime/EventRegistrationToken.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace System.Runtime.InteropServices.WindowsRuntime
1111
// Event registration tokens are 64 bit opaque structures returned from WinRT style event adders, in order
1212
// to signify a registration of a particular delegate to an event. The token's only real use is to
1313
// unregister the same delgate from the event at a later time.
14-
public struct EventRegistrationToken
14+
public struct EventRegistrationToken : IEquatable<EventRegistrationToken>
1515
{
1616
internal ulong m_value;
1717

@@ -49,5 +49,15 @@ public override int GetHashCode()
4949
{
5050
return m_value.GetHashCode();
5151
}
52+
53+
bool IEquatable<EventRegistrationToken>.Equals(EventRegistrationToken other)
54+
{
55+
return Equals(other);
56+
}
57+
58+
private bool Equals(EventRegistrationToken other)
59+
{
60+
return other.m_value == m_value;
61+
}
5262
}
5363
}

0 commit comments

Comments
 (0)