From 3e737f5a233594e293c140d0dddc20996e19821b Mon Sep 17 00:00:00 2001 From: aw0lid Date: Fri, 24 Apr 2026 01:57:37 +0300 Subject: [PATCH] Optimize ImmutableHashSet.IsProperSubsetOf to avoid unnecessary allocations --- .../Immutable/ImmutableHashSet_1.cs | 97 +++++++++++++------ 1 file changed, 70 insertions(+), 27 deletions(-) diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs index c6b6f3140e4cc2..d871832a372171 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableHashSet_1.cs @@ -296,6 +296,20 @@ public bool SetEquals(IEnumerable other) public bool IsProperSubsetOf(IEnumerable other) { Requires.NotNull(other, nameof(other)); + if (this.Origin.Root.IsEmpty) + { + return other.Any(); + } + + if (other is ICollection otherAsICollectionGeneric) + { + return IsProperSubsetOfFastPath(otherAsICollectionGeneric, this.Origin); + } + + else if (other is ICollection otherAsICollection && otherAsICollection.Count <= this.Origin.Count) + { + return false; + } return IsProperSubsetOf(other, this.Origin); } @@ -863,49 +877,78 @@ private static MutationResult SymmetricExcept(IEnumerable other, MutationInpu /// /// Performs the set operation on a given data structure. /// - private static bool IsProperSubsetOf(IEnumerable other, MutationInput origin) + private static bool IsProperSubsetOfFastPath(ICollection other, MutationInput origin) { Requires.NotNull(other, nameof(other)); - if (origin.Root.IsEmpty) - { - return other.Any(); - } - - // To determine whether everything we have is also in another sequence, - // we enumerate the sequence and "tag" whether it's in this collection, - // then consider whether every element in this collection was tagged. - // Since this collection is immutable we cannot directly tag. So instead - // we simply count how many "hits" we have and ensure it's equal to the - // size of this collection. Of course for this to work we need to ensure - // the uniqueness of items in the given sequence, so we create a set based - // on the sequence first. - var otherSet = new HashSet(other, origin.EqualityComparer); - if (origin.Count >= otherSet.Count) + if (other.Count <= origin.Count) { return false; } - int matches = 0; - bool extraFound = false; - foreach (T item in otherSet) + if (other is ImmutableHashSet otherAsImmutableHashSet) { - if (Contains(item, origin)) - { - matches++; - } - else + if (otherAsImmutableHashSet.KeyComparer == origin.EqualityComparer) { - extraFound = true; + using (var e = new ImmutableHashSet.Enumerator(origin.Root)) + { + while (e.MoveNext()) + { + if (!otherAsImmutableHashSet.Contains(e.Current)) + { + return false; + } + } + } + return true; } + } - if (matches == origin.Count && extraFound) + else if (other is HashSet otherAsHashSet) + { + if (otherAsHashSet.Comparer == origin.EqualityComparer) { + using (var e = new ImmutableHashSet.Enumerator(origin.Root)) + { + while (e.MoveNext()) + { + if (!otherAsHashSet.Contains(e.Current)) + { + return false; + } + } + } return true; } } - return false; + return IsProperSubsetOf(other, origin); + } + + /// + /// Performs the set operation on a given data structure. + /// + private static bool IsProperSubsetOf(IEnumerable other, MutationInput origin) + { + Requires.NotNull(other, nameof(other)); + + var otherSet = new HashSet(other, origin.EqualityComparer); + if (otherSet.Count <= origin.Count) + { + return false; + } + + using (var e = new ImmutableHashSet.Enumerator(origin.Root)) + { + while (e.MoveNext()) + { + if (!otherSet.Contains(e.Current)) + { + return false; + } + } + } + return true; } ///