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

Commit 39342a0

Browse files
committed
Merge pull request #1950 from stephentoub/immutsortedset_perf
Optimize ImmutableSortedSet<T>.LeafToRootRefill
2 parents cc7f544 + 73c1274 commit 39342a0

File tree

2 files changed

+86
-26
lines changed

2 files changed

+86
-26
lines changed

src/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableSortedSet`1.cs

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,10 +1102,52 @@ private ImmutableSortedSet<T> LeafToRootRefill(IEnumerable<T> addedItems)
11021102
// and can index into that sequence like a list, so the limited
11031103
// garbage produced is a temporary mutable data structure we use
11041104
// as a reference when creating the immutable one.
1105-
// The (mutable) SortedSet<T> is much faster at constructing its collection
1106-
// when passed a sequence into its constructor than into its Union method.
1107-
var sortedSet = new SortedSet<T>(this.Concat(addedItems), this.KeyComparer);
1108-
Node root = Node.NodeTreeFromSortedSet(sortedSet);
1105+
1106+
// Produce the initial list containing all elements, including any duplicates.
1107+
List<T> list;
1108+
if (this.IsEmpty)
1109+
{
1110+
// If the additional items enumerable list is known to be empty, too,
1111+
// then just return this empty instance.
1112+
int count;
1113+
if (addedItems.TryGetCount(out count) && count == 0)
1114+
{
1115+
return this;
1116+
}
1117+
1118+
// Otherwise, construct a list from the items. The Count could still
1119+
// be zero, in which case, again, just return this empty instance.
1120+
list = new List<T>(addedItems);
1121+
if (list.Count == 0)
1122+
{
1123+
return this;
1124+
}
1125+
}
1126+
else
1127+
{
1128+
// Build the list from this set and then add the additional items.
1129+
// Even if the additional items is empty, this set isn't, so we know
1130+
// the resulting list will not be empty.
1131+
list = new List<T>(this);
1132+
list.AddRange(addedItems);
1133+
}
1134+
Debug.Assert(list.Count > 0);
1135+
1136+
// Sort the list and remove duplicate entries.
1137+
IComparer<T> comparer = this.KeyComparer;
1138+
list.Sort(comparer);
1139+
int index = 1;
1140+
for (int i = 1; i < list.Count; i++)
1141+
{
1142+
if (comparer.Compare(list[i], list[i - 1]) != 0)
1143+
{
1144+
list[index++] = list[i];
1145+
}
1146+
}
1147+
list.RemoveRange(index, list.Count - index);
1148+
1149+
// Use the now sorted list of unique items to construct a new sorted set.
1150+
Node root = Node.NodeTreeFromList(list.AsOrderedCollection(), 0, list.Count);
11091151
return this.Wrap(root);
11101152
}
11111153

@@ -1673,26 +1715,6 @@ internal Enumerator GetEnumerator(Builder builder)
16731715
return new Enumerator(this, builder);
16741716
}
16751717

1676-
/// <summary>
1677-
/// Creates a node tree from an existing (mutable) collection.
1678-
/// </summary>
1679-
/// <param name="collection">The collection.</param>
1680-
/// <returns>The root of the node tree.</returns>
1681-
[Pure]
1682-
internal static Node NodeTreeFromSortedSet(SortedSet<T> collection)
1683-
{
1684-
Requires.NotNull(collection, "collection");
1685-
Contract.Ensures(Contract.Result<Node>() != null);
1686-
1687-
if (collection.Count == 0)
1688-
{
1689-
return EmptyNode;
1690-
}
1691-
1692-
var list = collection.AsOrderedCollection();
1693-
return NodeTreeFromList(list, 0, list.Count);
1694-
}
1695-
16961718
/// <summary>
16971719
/// See the <see cref="ICollection{T}"/> interface.
16981720
/// </summary>
@@ -2128,7 +2150,7 @@ private static Node MakeBalanced(Node tree)
21282150
/// <param name="length">The number of elements from <paramref name="items"/> that should be captured by the node tree.</param>
21292151
/// <returns>The root of the created node tree.</returns>
21302152
[Pure]
2131-
private static Node NodeTreeFromList(IOrderedCollection<T> items, int start, int length)
2153+
internal static Node NodeTreeFromList(IOrderedCollection<T> items, int start, int length)
21322154
{
21332155
Requires.NotNull(items, "items");
21342156
Debug.Assert(start >= 0);

src/System.Collections.Immutable/tests/ImmutableSortedSetTest.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,51 @@ public void ToUnorderedTest()
121121
}
122122

123123
[Fact]
124-
public void ToImmutableSortedSetTest()
124+
public void ToImmutableSortedSetFromArrayTest()
125125
{
126126
var set = new[] { 1, 2, 2 }.ToImmutableSortedSet();
127127
Assert.Same(Comparer<int>.Default, set.KeyComparer);
128128
Assert.Equal(2, set.Count);
129129
}
130130

131+
[Theory]
132+
[InlineData(new int[] { }, new int[] { })]
133+
[InlineData(new int[] { 1 }, new int[] { 1 })]
134+
[InlineData(new int[] { 1, 1 }, new int[] { 1 })]
135+
[InlineData(new int[] { 1, 1, 1 }, new int[] { 1 })]
136+
[InlineData(new int[] { 1, 2, 3 }, new int[] { 1, 2, 3 })]
137+
[InlineData(new int[] { 3, 2, 1 }, new int[] { 1, 2, 3 })]
138+
[InlineData(new int[] { 1, 1, 3 }, new int[] { 1, 3 })]
139+
[InlineData(new int[] { 1, 2, 2 }, new int[] { 1, 2 })]
140+
[InlineData(new int[] { 1, 2, 2, 3, 3, 3 }, new int[] { 1, 2, 3 })]
141+
[InlineData(new int[] { 1, 2, 3, 1, 2, 3 }, new int[] { 1, 2, 3 })]
142+
[InlineData(new int[] { 1, 1, 2, 2, 2, 3, 3, 3, 3 }, new int[] { 1, 2, 3 })]
143+
public void ToImmutableSortedSetFromEnumerableTest(int[] input, int[] expectedOutput)
144+
{
145+
IEnumerable<int> enumerableInput = input.Select(i => i); // prevent querying for indexable interfaces
146+
var set = enumerableInput.ToImmutableSortedSet();
147+
Assert.Equal((IEnumerable<int>)expectedOutput, set.ToArray());
148+
}
149+
150+
[Theory]
151+
[InlineData(new int[] { }, new int[] { 1 })]
152+
[InlineData(new int[] { 1 }, new int[] { 1 })]
153+
[InlineData(new int[] { 1, 1 }, new int[] { 1 })]
154+
[InlineData(new int[] { 1, 1, 1 }, new int[] { 1 })]
155+
[InlineData(new int[] { 1, 2, 3 }, new int[] { 1, 2, 3 })]
156+
[InlineData(new int[] { 3, 2, 1 }, new int[] { 1, 2, 3 })]
157+
[InlineData(new int[] { 1, 1, 3 }, new int[] { 1, 3 })]
158+
[InlineData(new int[] { 1, 2, 2 }, new int[] { 1, 2 })]
159+
[InlineData(new int[] { 1, 2, 2, 3, 3, 3 }, new int[] { 1, 2, 3 })]
160+
[InlineData(new int[] { 1, 2, 3, 1, 2, 3 }, new int[] { 1, 2, 3 })]
161+
[InlineData(new int[] { 1, 1, 2, 2, 2, 3, 3, 3, 3 }, new int[] { 1, 2, 3 })]
162+
public void UnionWithEnumerableTest(int[] input, int[] expectedOutput)
163+
{
164+
IEnumerable<int> enumerableInput = input.Select(i => i); // prevent querying for indexable interfaces
165+
var set = ImmutableSortedSet.Create(1).Union(enumerableInput);
166+
Assert.Equal((IEnumerable<int>)expectedOutput, set.ToArray());
167+
}
168+
131169
[Fact]
132170
public void IndexOfTest()
133171
{

0 commit comments

Comments
 (0)