This repository was archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Improve SortedSet performance #1955
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
85 changes: 85 additions & 0 deletions
85
src/Common/src/System/Collections/Generic/EnumerableHelpers.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// Copyright (c) Microsoft. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
namespace System.Collections.Generic | ||
{ | ||
/// <summary>Internal helper functions for working with enumerables.</summary> | ||
internal static class EnumerableHelpers | ||
{ | ||
/// <summary>Converts an enumerable to an array using the same logic as does List{T}.</summary> | ||
/// <param name="length">The number of items stored in the resulting array, 0-indexed.</param> | ||
/// <returns> | ||
/// The resulting array. The length of the array may be greater than <paramref name="length"/>, | ||
/// which is the actual number of elements in the array. | ||
/// </returns> | ||
internal static T[] ToArray<T>(IEnumerable<T> source, out int length) | ||
{ | ||
T[] arr; | ||
int count = 0; | ||
|
||
ICollection<T> ic = source as ICollection<T>; | ||
if (ic != null) | ||
{ | ||
count = ic.Count; | ||
if (count == 0) | ||
{ | ||
arr = Array.Empty<T>(); | ||
} | ||
else | ||
{ | ||
// Allocate an array of the desired size, then copy the elements into it. Note that this has the same | ||
// issue regarding concurrency as other existing collections like List<T>. If the collection size | ||
// concurrently changes between the array allocation and the CopyTo, we could end up either getting an | ||
// exception from overrunning the array (if the size went up) or we could end up not filling as many | ||
// items as 'count' suggests (if the size went down). This is only an issue for concurrent collections | ||
// that implement ICollection<T>, which as of .NET 4.6 is just ConcurrentDictionary<TKey, TValue>. | ||
arr = new T[count]; | ||
ic.CopyTo(arr, 0); | ||
} | ||
} | ||
else | ||
{ | ||
arr = Array.Empty<T>(); | ||
foreach (var item in source) | ||
{ | ||
if (count == arr.Length) | ||
{ | ||
// MaxArrayLength is defined in Array.MaxArrayLength and in gchelpers in CoreCLR. | ||
// It represents the maximum number of elements that can be in an array where | ||
// the size of the element is greater than one byte; a separate, slightly larger constant, | ||
// is used when the size of the element is one. | ||
const int MaxArrayLength = 0x7FEFFFFF; | ||
|
||
// This is the same growth logic as in List<T>: | ||
// If the array is currently empty, we make it a default size. Otherwise, we attempt to | ||
// double the size of the array. Doubling will overflow once the size of the array reaches | ||
// 2^30, since doubling to 2^31 is 1 larger than Int32.MaxValue. In that case, we instead | ||
// constrain the length to be MaxArrayLength (this overflow check works because of of the | ||
// cast to uint). Because a slightly larger constant is used when T is one byte in size, we | ||
// could then end up in a situation where arr.Length is MaxArrayLength or slightly larger, such | ||
// that we constrain newLength to be MaxArrayLength but the needed number of elements is actually | ||
// larger than that. For that case, we then ensure that the newLength is large enough to hold | ||
// the desired capacity. This does mean that in the very rare case where we've grown to such a | ||
// large size, each new element added after MaxArrayLength will end up doing a resize. | ||
const int DefaultCapacity = 4; | ||
int newLength = count == 0 ? DefaultCapacity : count * 2; | ||
if ((uint)newLength > MaxArrayLength) | ||
{ | ||
newLength = MaxArrayLength; | ||
} | ||
if (newLength < count + 1) | ||
{ | ||
newLength = count + 1; | ||
} | ||
|
||
arr = ArrayT<T>.Resize(arr, newLength, count); | ||
} | ||
arr[count++] = item; | ||
} | ||
} | ||
|
||
length = count; | ||
return arr; | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -95,13 +95,9 @@ public SortedSet(IEnumerable<T> collection, IComparer<T> comparer) | |
//breadth first traversal to recreate nodes | ||
if (baseSortedSet.Count == 0) | ||
{ | ||
_count = 0; | ||
_version = 0; | ||
_root = null; | ||
return; | ||
} | ||
|
||
|
||
//pre order way to replicate nodes | ||
Stack<Node> theirStack = new Stack<SortedSet<T>.Node>(2 * log2(baseSortedSet.Count) + 2); | ||
Stack<Node> myStack = new Stack<SortedSet<T>.Node>(2 * log2(baseSortedSet.Count) + 2); | ||
|
@@ -138,23 +134,27 @@ public SortedSet(IEnumerable<T> collection, IComparer<T> comparer) | |
} | ||
} | ||
_count = baseSortedSet._count; | ||
_version = 0; | ||
} | ||
else | ||
{ //As it stands, you're doing an NlogN sort of the collection | ||
List<T> els = new List<T>(collection); | ||
els.Sort(_comparer); | ||
for (int i = 1; i < els.Count; i++) | ||
{ | ||
int count; | ||
T[] els = EnumerableHelpers.ToArray(collection, out count); | ||
if (count > 0) | ||
{ | ||
if (comparer.Compare(els[i], els[i - 1]) == 0) | ||
Array.Sort(els, 0, count, _comparer); | ||
int index = 1; | ||
for (int i = 1; i < count; i++) | ||
{ | ||
els.RemoveAt(i); | ||
i--; | ||
if (comparer.Compare(els[i], els[i - 1]) != 0) | ||
{ | ||
els[index++] = els[i]; | ||
} | ||
} | ||
count = index; | ||
|
||
_root = ConstructRootFromSortedArray(els, 0, count - 1, null); | ||
_count = count; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You no longer need to set the version? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the constructor. Setting |
||
_root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null); | ||
_count = els.Count; | ||
_version = 0; | ||
} | ||
} | ||
|
||
|
@@ -257,25 +257,24 @@ internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate<T> action) | |
return true; | ||
} | ||
|
||
List<Node> processQueue = new List<Node>(); | ||
processQueue.Add(_root); | ||
Queue<Node> processQueue = new Queue<Node>(); | ||
processQueue.Enqueue(_root); | ||
Node current; | ||
|
||
while (processQueue.Count != 0) | ||
{ | ||
current = processQueue[0]; | ||
processQueue.RemoveAt(0); | ||
current = processQueue.Dequeue(); | ||
if (!action(current)) | ||
{ | ||
return false; | ||
} | ||
if (current.Left != null) | ||
{ | ||
processQueue.Add(current.Left); | ||
processQueue.Enqueue(current.Left); | ||
} | ||
if (current.Right != null) | ||
{ | ||
processQueue.Add(current.Right); | ||
processQueue.Enqueue(current.Right); | ||
} | ||
} | ||
return true; | ||
|
@@ -2084,25 +2083,24 @@ internal override bool BreadthFirstTreeWalk(TreeWalkPredicate<T> action) | |
return true; | ||
} | ||
|
||
List<Node> processQueue = new List<Node>(); | ||
processQueue.Add(_root); | ||
Queue<Node> processQueue = new Queue<Node>(); | ||
processQueue.Enqueue(_root); | ||
Node current; | ||
|
||
while (processQueue.Count != 0) | ||
{ | ||
current = processQueue[0]; | ||
processQueue.RemoveAt(0); | ||
current = processQueue.Dequeue(); | ||
if (IsWithinRange(current.Item) && !action(current)) | ||
{ | ||
return false; | ||
} | ||
if (current.Left != null && (!_lBoundActive || Comparer.Compare(_min, current.Item) < 0)) | ||
{ | ||
processQueue.Add(current.Left); | ||
processQueue.Enqueue(current.Left); | ||
} | ||
if (current.Right != null && (!_uBoundActive || Comparer.Compare(_max, current.Item) > 0)) | ||
{ | ||
processQueue.Add(current.Right); | ||
processQueue.Enqueue(current.Right); | ||
} | ||
} | ||
return true; | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 this should probably have been part of a different commit.