Skip to content

Commit 341cea9

Browse files
committed
Apply optimizations from RawSet
1 parent c70f216 commit 341cea9

File tree

1 file changed

+177
-29
lines changed

1 file changed

+177
-29
lines changed

Badeend.ValueCollections/Internals/RawDictionary.cs

Lines changed: 177 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,52 @@ internal readonly ref TValue GetValueRefOrNullRef(TKey key)
387387
goto Return;
388388
}
389389

390+
private readonly int GetKeyIndex(TKey key)
391+
{
392+
if (key == null)
393+
{
394+
ThrowHelpers.ThrowArgumentNullException(ThrowHelpers.Argument.key);
395+
}
396+
397+
ref Entry entry = ref Polyfills.NullRef<Entry>();
398+
if (this.buckets != null)
399+
{
400+
Polyfills.DebugAssert(this.entries != null, "expected entries to be != null");
401+
402+
var comparer = new DefaultEqualityComparer<TKey>();
403+
uint hashCode = (uint)comparer.GetHashCode(key);
404+
int i = this.GetBucketRef(hashCode);
405+
var entries = this.entries;
406+
uint collisionCount = 0;
407+
i--; // Value in _buckets is 1-based; subtract 1 from i. We do it here so it fuses with the following conditional.
408+
do
409+
{
410+
// Test in if to drop range check for following array access
411+
if ((uint)i >= (uint)entries.Length)
412+
{
413+
return -1;
414+
}
415+
416+
entry = ref entries[i];
417+
if (entry.HashCode == hashCode && comparer.Equals(entry.Key, key))
418+
{
419+
return i;
420+
}
421+
422+
i = entry.Next;
423+
424+
collisionCount++;
425+
}
426+
while (collisionCount <= (uint)entries.Length);
427+
428+
// The chain of entries forms a loop; which means a concurrent update has happened.
429+
// Break out of the loop and throw, rather than looping forever.
430+
ThrowHelpers.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
431+
}
432+
433+
return -1;
434+
}
435+
390436
private int Initialize(int minimumCapacity)
391437
{
392438
var capacity = HashHelpers.GetPrime(minimumCapacity);
@@ -842,22 +888,38 @@ internal readonly bool Keys_IsProperSubsetOf(IEnumerable<TKey> other)
842888
ThrowHelpers.ThrowArgumentNullException(ThrowHelpers.Argument.other);
843889
}
844890

845-
var otherCopy = new RawSet<TKey>(other); // TODO: don't copy
846-
847-
if (this.Count >= otherCopy.Count)
891+
if (Polyfills.TryGetNonEnumeratedCount(other, out var otherCount))
848892
{
849-
return false;
893+
var count = this.Count;
894+
895+
// No set is a proper subset of a set with less or equal number of elements.
896+
if (otherCount <= count)
897+
{
898+
return false;
899+
}
900+
901+
// The empty set is a proper subset of anything but the empty set.
902+
if (count == 0)
903+
{
904+
// Based on check above, other is not empty when Count == 0.
905+
return true;
906+
}
850907
}
851908

852-
foreach (var entry in this)
909+
using var marker = new Marker(in this);
910+
911+
var otherHasAdditionalItems = false;
912+
913+
// Note that enumerating `other` might trigger mutations on `this`.
914+
foreach (var item in other)
853915
{
854-
if (!otherCopy.Contains(entry.Key))
916+
if (!marker.Mark(item))
855917
{
856-
return false;
918+
otherHasAdditionalItems = true;
857919
}
858920
}
859921

860-
return true;
922+
return marker.UnmarkedCount == 0 && otherHasAdditionalItems;
861923
}
862924

863925
// Does not check/guard against concurrent mutation during enumeration!
@@ -868,24 +930,36 @@ internal readonly bool Keys_IsProperSupersetOf(IEnumerable<TKey> other)
868930
ThrowHelpers.ThrowArgumentNullException(ThrowHelpers.Argument.other);
869931
}
870932

871-
if (this.Count == 0)
933+
var count = this.Count;
934+
935+
// The empty set isn't a proper superset of any set.
936+
if (count == 0)
872937
{
873938
return false;
874939
}
875940

876-
var otherCopy = new RawSet<TKey>(other); // TODO: don't copy
941+
if (Polyfills.TryGetNonEnumeratedCount(other, out var otherCount))
942+
{
943+
// If other is the empty set then this is a superset.
944+
if (otherCount == 0)
945+
{
946+
// Note that this has at least one element, based on above check.
947+
return true;
948+
}
949+
}
877950

878-
int matchCount = 0;
879-
foreach (var item in otherCopy)
951+
using var marker = new Marker(in this);
952+
953+
// Note that enumerating `other` might trigger mutations on `this`.
954+
foreach (var item in other)
880955
{
881-
matchCount++;
882-
if (!this.ContainsKey(item))
956+
if (!marker.Mark(item))
883957
{
884958
return false;
885959
}
886960
}
887961

888-
return this.Count > matchCount;
962+
return marker.UnmarkedCount > 0;
889963
}
890964

891965
// Does not check/guard against concurrent mutation during enumeration!
@@ -896,22 +970,31 @@ internal readonly bool Keys_IsSubsetOf(IEnumerable<TKey> other)
896970
ThrowHelpers.ThrowArgumentNullException(ThrowHelpers.Argument.other);
897971
}
898972

899-
var otherCopy = new RawSet<TKey>(other); // TODO: don't copy
900-
901-
if (this.Count > otherCopy.Count)
973+
// The empty set is a subset of any set.
974+
var count = this.Count;
975+
if (count == 0)
902976
{
903-
return false;
977+
return true;
904978
}
905979

906-
foreach (var entry in this)
980+
if (Polyfills.TryGetNonEnumeratedCount(other, out var otherCount))
907981
{
908-
if (!otherCopy.Contains(entry.Key))
982+
// If this has more elements then it can't be a subset.
983+
if (count > otherCount)
909984
{
910985
return false;
911986
}
912987
}
913988

914-
return true;
989+
using var marker = new Marker(in this);
990+
991+
// Note that enumerating `other` might trigger mutations on `this`.
992+
foreach (var item in other)
993+
{
994+
marker.Mark(item);
995+
}
996+
997+
return marker.UnmarkedCount == 0;
915998
}
916999

9171000
// Does not check/guard against concurrent mutation during enumeration!
@@ -965,22 +1048,35 @@ internal readonly bool Keys_SetEquals(IEnumerable<TKey> other)
9651048
ThrowHelpers.ThrowArgumentNullException(ThrowHelpers.Argument.other);
9661049
}
9671050

968-
var otherCopy = new RawSet<TKey>(other); // TODO: don't copy
969-
970-
if (this.Count != otherCopy.Count)
1051+
if (Polyfills.TryGetNonEnumeratedCount(other, out var otherCount))
9711052
{
972-
return false;
1053+
var count = this.Count;
1054+
1055+
// If this is empty, they are equal iff other is empty.
1056+
if (count == 0)
1057+
{
1058+
return otherCount == 0;
1059+
}
1060+
1061+
// Can't be equal if other set contains fewer elements than this.
1062+
if (count > otherCount)
1063+
{
1064+
return false;
1065+
}
9731066
}
9741067

975-
foreach (var item in otherCopy)
1068+
using var marker = new Marker(in this);
1069+
1070+
// Note that enumerating `other` might trigger mutations on `this`.
1071+
foreach (var item in other)
9761072
{
977-
if (!this.ContainsKey(item))
1073+
if (!marker.Mark(item))
9781074
{
9791075
return false;
9801076
}
9811077
}
9821078

983-
return true;
1079+
return marker.UnmarkedCount == 0;
9841080
}
9851081

9861082
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1070,6 +1166,58 @@ public readonly override string ToString()
10701166
return builder.ToString();
10711167
}
10721168

1169+
internal ref struct Marker
1170+
{
1171+
private readonly RawDictionary<TKey, TValue> dictionary;
1172+
private readonly RawBitArray markings;
1173+
private int unmarked;
1174+
1175+
/// <summary>
1176+
/// How many elements in the set have not been marked yet.
1177+
/// </summary>
1178+
public readonly int UnmarkedCount
1179+
{
1180+
get
1181+
{
1182+
Polyfills.DebugAssert(this.unmarked >= 0);
1183+
1184+
return this.unmarked;
1185+
}
1186+
}
1187+
1188+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1189+
public Marker(ref readonly RawDictionary<TKey, TValue> set)
1190+
{
1191+
this.dictionary = set;
1192+
this.markings = new RawBitArray(set.end);
1193+
this.unmarked = set.Count;
1194+
}
1195+
1196+
// The RawSet may not be mutated in between calls to Mark.
1197+
public bool Mark(TKey key)
1198+
{
1199+
int index = this.dictionary.GetKeyIndex(key);
1200+
if (index < 0)
1201+
{
1202+
return false;
1203+
}
1204+
1205+
if (!this.markings[index])
1206+
{
1207+
this.markings[index] = true;
1208+
this.unmarked--;
1209+
}
1210+
1211+
return true;
1212+
}
1213+
1214+
#pragma warning disable CA1822 // Mark members as static
1215+
public void Dispose()
1216+
{
1217+
}
1218+
#pragma warning restore CA1822 // Mark members as static
1219+
}
1220+
10731221
private readonly int GetArbitraryIndex()
10741222
{
10751223
// Use the hashcode of the backing `entries` array as a semi-random seed.

0 commit comments

Comments
 (0)