@@ -51,6 +51,9 @@ private struct Entry
51
51
52
52
private int [ ] ? _buckets ;
53
53
private Entry [ ] ? _entries ;
54
+ #if BIT64
55
+ private ulong _fastModMultiplier ;
56
+ #endif
54
57
private int _count ;
55
58
private int _freeList ;
56
59
private int _freeCount ;
@@ -330,16 +333,15 @@ private ref TValue FindValue(TKey key)
330
333
ThrowHelper . ThrowArgumentNullException ( ExceptionArgument . key ) ;
331
334
}
332
335
333
- int [ ] ? buckets = _buckets ;
334
336
ref Entry entry = ref Unsafe . NullRef < Entry > ( ) ;
335
- if ( buckets != null )
337
+ if ( _buckets != null )
336
338
{
337
339
Debug . Assert ( _entries != null , "expected entries to be != null" ) ;
338
340
IEqualityComparer < TKey > ? comparer = _comparer ;
339
341
if ( comparer == null )
340
342
{
341
343
uint hashCode = ( uint ) key . GetHashCode ( ) ;
342
- int i = buckets [ hashCode % ( uint ) buckets . Length ] ;
344
+ int i = GetBucket ( hashCode ) ;
343
345
Entry [ ] ? entries = _entries ;
344
346
uint collisionCount = 0 ;
345
347
if ( default ( TKey ) ! != null ) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757)
@@ -407,7 +409,7 @@ private ref TValue FindValue(TKey key)
407
409
else
408
410
{
409
411
uint hashCode = ( uint ) comparer . GetHashCode ( key ) ;
410
- int i = buckets [ hashCode % ( uint ) buckets . Length ] ;
412
+ int i = GetBucket ( hashCode ) ;
411
413
Entry [ ] ? entries = _entries ;
412
414
uint collisionCount = 0 ;
413
415
// Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
@@ -453,10 +455,16 @@ private ref TValue FindValue(TKey key)
453
455
private int Initialize ( int capacity )
454
456
{
455
457
int size = HashHelpers . GetPrime ( capacity ) ;
458
+ int [ ] buckets = new int [ size ] ;
459
+ Entry [ ] entries = new Entry [ size ] ;
456
460
461
+ // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails
457
462
_freeList = - 1 ;
458
- _buckets = new int [ size ] ;
459
- _entries = new Entry [ size ] ;
463
+ #if BIT64
464
+ _fastModMultiplier = HashHelpers . GetFastModMultiplier ( ( uint ) size ) ;
465
+ #endif
466
+ _buckets = buckets ;
467
+ _entries = entries ;
460
468
461
469
return size ;
462
470
}
@@ -481,7 +489,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
481
489
uint hashCode = ( uint ) ( ( comparer == null ) ? key . GetHashCode ( ) : comparer . GetHashCode ( key ) ) ;
482
490
483
491
uint collisionCount = 0 ;
484
- ref int bucket = ref _buckets [ hashCode % ( uint ) _buckets . Length ] ;
492
+ ref int bucket = ref GetBucket ( hashCode ) ;
485
493
// Value in _buckets is 1-based
486
494
int i = bucket - 1 ;
487
495
@@ -625,7 +633,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
625
633
if ( count == entries . Length )
626
634
{
627
635
Resize ( ) ;
628
- bucket = ref _buckets [ hashCode % ( uint ) _buckets . Length ] ;
636
+ bucket = ref GetBucket ( hashCode ) ;
629
637
}
630
638
index = count ;
631
639
_count = count + 1 ;
@@ -716,7 +724,6 @@ private void Resize(int newSize, bool forceNewHashCodes)
716
724
Debug . Assert ( _entries != null , "_entries should be non-null" ) ;
717
725
Debug . Assert ( newSize >= _entries . Length ) ;
718
726
719
- int [ ] buckets = new int [ newSize ] ;
720
727
Entry [ ] entries = new Entry [ newSize ] ;
721
728
722
729
int count = _count ;
@@ -734,19 +741,23 @@ private void Resize(int newSize, bool forceNewHashCodes)
734
741
}
735
742
}
736
743
744
+ // Assign member variables after both arrays allocated to guard against corruption from OOM if second fails
745
+ _buckets = new int [ newSize ] ;
746
+ #if BIT64
747
+ _fastModMultiplier = HashHelpers . GetFastModMultiplier ( ( uint ) newSize ) ;
748
+ #endif
737
749
for ( int i = 0 ; i < count ; i ++ )
738
750
{
739
751
if ( entries [ i ] . next >= - 1 )
740
752
{
741
- uint bucket = entries [ i ] . hashCode % ( uint ) newSize ;
753
+ ref int bucket = ref GetBucket ( entries [ i ] . hashCode ) ;
742
754
// Value in _buckets is 1-based
743
- entries [ i ] . next = buckets [ bucket ] - 1 ;
755
+ entries [ i ] . next = bucket - 1 ;
744
756
// Value in _buckets is 1-based
745
- buckets [ bucket ] = i + 1 ;
757
+ bucket = i + 1 ;
746
758
}
747
759
}
748
760
749
- _buckets = buckets ;
750
761
_entries = entries ;
751
762
}
752
763
@@ -760,17 +771,16 @@ public bool Remove(TKey key)
760
771
ThrowHelper . ThrowArgumentNullException ( ExceptionArgument . key ) ;
761
772
}
762
773
763
- int [ ] ? buckets = _buckets ;
764
- Entry [ ] ? entries = _entries ;
765
- if ( buckets != null )
774
+ if ( _buckets != null )
766
775
{
767
- Debug . Assert ( entries != null , "entries should be non-null" ) ;
776
+ Debug . Assert ( _entries != null , "entries should be non-null" ) ;
768
777
uint collisionCount = 0 ;
769
778
uint hashCode = ( uint ) ( _comparer ? . GetHashCode ( key ) ?? key . GetHashCode ( ) ) ;
770
- uint bucket = hashCode % ( uint ) buckets . Length ;
779
+ ref int bucket = ref GetBucket ( hashCode ) ;
780
+ Entry [ ] ? entries = _entries ;
771
781
int last = - 1 ;
772
782
// Value in buckets is 1-based
773
- int i = buckets [ bucket ] - 1 ;
783
+ int i = bucket - 1 ;
774
784
while ( i >= 0 )
775
785
{
776
786
ref Entry entry = ref entries [ i ] ;
@@ -780,7 +790,7 @@ public bool Remove(TKey key)
780
790
if ( last < 0 )
781
791
{
782
792
// Value in buckets is 1-based
783
- buckets [ bucket ] = entry . next + 1 ;
793
+ bucket = entry . next + 1 ;
784
794
}
785
795
else
786
796
{
@@ -829,17 +839,16 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
829
839
ThrowHelper . ThrowArgumentNullException ( ExceptionArgument . key ) ;
830
840
}
831
841
832
- int [ ] ? buckets = _buckets ;
833
- Entry [ ] ? entries = _entries ;
834
- if ( buckets != null )
842
+ if ( _buckets != null )
835
843
{
836
- Debug . Assert ( entries != null , "entries should be non-null" ) ;
844
+ Debug . Assert ( _entries != null , "entries should be non-null" ) ;
837
845
uint collisionCount = 0 ;
838
846
uint hashCode = ( uint ) ( _comparer ? . GetHashCode ( key ) ?? key . GetHashCode ( ) ) ;
839
- uint bucket = hashCode % ( uint ) buckets . Length ;
847
+ ref int bucket = ref GetBucket ( hashCode ) ;
848
+ Entry [ ] ? entries = _entries ;
840
849
int last = - 1 ;
841
850
// Value in buckets is 1-based
842
- int i = buckets [ bucket ] - 1 ;
851
+ int i = bucket - 1 ;
843
852
while ( i >= 0 )
844
853
{
845
854
ref Entry entry = ref entries [ i ] ;
@@ -849,7 +858,7 @@ public bool Remove(TKey key, [MaybeNullWhen(false)] out TValue value)
849
858
if ( last < 0 )
850
859
{
851
860
// Value in buckets is 1-based
852
- buckets [ bucket ] = entry . next + 1 ;
861
+ bucket = entry . next + 1 ;
853
862
}
854
863
else
855
864
{
@@ -982,6 +991,7 @@ public int EnsureCapacity(int capacity)
982
991
_version ++ ;
983
992
if ( _buckets == null )
984
993
return Initialize ( capacity ) ;
994
+
985
995
int newSize = HashHelpers . GetPrime ( capacity ) ;
986
996
Resize ( newSize , forceNewHashCodes : false ) ;
987
997
return newSize ;
@@ -1011,8 +1021,8 @@ public void TrimExcess(int capacity)
1011
1021
{
1012
1022
if ( capacity < Count )
1013
1023
ThrowHelper . ThrowArgumentOutOfRangeException ( ExceptionArgument . capacity ) ;
1014
- int newSize = HashHelpers . GetPrime ( capacity ) ;
1015
1024
1025
+ int newSize = HashHelpers . GetPrime ( capacity ) ;
1016
1026
Entry [ ] ? oldEntries = _entries ;
1017
1027
int currentCapacity = oldEntries == null ? 0 : oldEntries . Length ;
1018
1028
if ( newSize >= currentCapacity )
@@ -1022,7 +1032,6 @@ public void TrimExcess(int capacity)
1022
1032
_version ++ ;
1023
1033
Initialize ( newSize ) ;
1024
1034
Entry [ ] ? entries = _entries ;
1025
- int [ ] ? buckets = _buckets ;
1026
1035
int count = 0 ;
1027
1036
for ( int i = 0 ; i < oldCount ; i ++ )
1028
1037
{
@@ -1031,11 +1040,11 @@ public void TrimExcess(int capacity)
1031
1040
{
1032
1041
ref Entry entry = ref entries ! [ count ] ;
1033
1042
entry = oldEntries [ i ] ;
1034
- uint bucket = hashCode % ( uint ) newSize ;
1043
+ ref int bucket = ref GetBucket ( hashCode ) ;
1035
1044
// Value in _buckets is 1-based
1036
- entry . next = buckets ! [ bucket ] - 1 ; // If we get here, we have entries, therefore buckets is not null.
1045
+ entry . next = bucket - 1 ;
1037
1046
// Value in _buckets is 1-based
1038
- buckets [ bucket ] = count + 1 ;
1047
+ bucket = count + 1 ;
1039
1048
count ++ ;
1040
1049
}
1041
1050
}
@@ -1153,6 +1162,17 @@ void IDictionary.Remove(object key)
1153
1162
}
1154
1163
}
1155
1164
1165
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
1166
+ private ref int GetBucket ( uint hashCode )
1167
+ {
1168
+ int [ ] buckets = _buckets ! ;
1169
+ #if BIT64
1170
+ return ref buckets [ HashHelpers . FastMod ( hashCode , ( uint ) buckets . Length , _fastModMultiplier ) ] ;
1171
+ #else
1172
+ return ref buckets [ hashCode % ( uint ) buckets . Length ] ;
1173
+ #endif
1174
+ }
1175
+
1156
1176
public struct Enumerator : IEnumerator < KeyValuePair < TKey , TValue > > ,
1157
1177
IDictionaryEnumerator
1158
1178
{
0 commit comments