-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
System.Linq Orderby performance optimization #46414
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -16,6 +16,8 @@ internal abstract partial class OrderedEnumerable<TElement> : IOrderedEnumerable | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private int[] SortedMap(Buffer<TElement> buffer) => GetEnumerableSorter().Sort(buffer._items, buffer._count); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private void SortElements(Buffer<TElement> buffer) => GetEnumerableSorter().SortElements(buffer._items, buffer._count); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private int[] SortedMap(Buffer<TElement> buffer, int minIdx, int maxIdx) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GetEnumerableSorter().Sort(buffer._items, buffer._count, minIdx, maxIdx); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -24,10 +26,21 @@ public IEnumerator<TElement> GetEnumerator() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Buffer<TElement> buffer = new Buffer<TElement>(_source); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (buffer._count > 0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
int[] map = SortedMap(buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (int i = 0; i < buffer._count; i++) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (CanInPlaceSort()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
yield return buffer._items[map[i]]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
SortElements(buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (int i = 0; i < buffer._count; i++) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
yield return buffer._items[i]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
int[] map = SortedMap(buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
for (int i = 0; i < buffer._count; i++) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
yield return buffer._items[map[i]]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -72,6 +85,8 @@ internal IEnumerator<TElement> GetEnumerator(int minIdx, int maxIdx) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
IOrderedEnumerable<TElement> IOrderedEnumerable<TElement>.CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey>? comparer, bool descending) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
new OrderedEnumerable<TElement, TKey>(_source, keySelector, comparer, @descending, this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected abstract bool CanInPlaceSort(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public TElement? TryGetLast(Func<TElement, bool> predicate, out bool found) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
CachingComparer<TElement> comparer = GetComparer(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -158,6 +173,16 @@ internal override CachingComparer<TElement> GetComparer(CachingComparer<TElement | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: new CachingComparerWithChild<TElement, TKey>(_keySelector, _comparer, _descending, childComparer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return _parent != null ? _parent.GetComparer(cmp) : cmp; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected override bool CanInPlaceSort() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 would be a good place to write a comment detailing the motivations for this optimization. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (_parent != null) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Type elementType = typeof(TElement); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return elementType.IsValueType || (elementType.IsClass && !elementType.IsArray); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 expression could be optimized away by the JIT if you avoided assigning |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// A comparer that chains comparisons, and pushes through the last element found to be | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -262,6 +287,12 @@ internal int[] Sort(TElement[] elements, int count) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
internal void SortElements(TElement[] elements, int count) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
ComputeKeys(elements, count); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
QuickSortElements(elements, 0, count - 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
internal int[] Sort(TElement[] elements, int count, int minIdx, int maxIdx) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
int[] map = ComputeMap(elements, count); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -279,6 +310,8 @@ internal TElement ElementAt(TElement[] elements, int count, int idx) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected abstract void QuickSort(int[] map, int left, int right); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected abstract void QuickSortElements(TElement[] map, int left, int right); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Sorts the k elements between minIdx and maxIdx without sorting all elements | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Time complexity: O(n + k log k) best and average case. O(n^2) worse case. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected abstract void PartialQuickSort(int[] map, int left, int right, int minIdx, int maxIdx); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -343,6 +376,9 @@ internal override int CompareAnyKeys(int index1, int index2) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected override void QuickSort(int[] keys, int lo, int hi) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
new Span<int>(keys, lo, hi - lo + 1).Sort(CompareAnyKeys); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected override void QuickSortElements(TElement[] element, int lo, int hi) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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. Similarly, I would call this something like |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
new Span<TKey>(_keys, lo, hi - lo + 1).Sort(new Span<TElement>(element, lo, hi-lo +1), _comparer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
stephentoub marked this conversation as resolved.
Show resolved
Hide resolved
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 does not seem to be taking descending sorts into account. 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. Thanks for the catch, I tried using the overload taking Comparison<TKey> and made a function like CompareAnyKeys. This is causing a huge perf degredation, it appears that it is due to the additional method call to the Comparable<TKey> created in Sort and and the call to new new Comparison<TKey>. Since Sort doesn't support descending the only way to maintain this Perf is to restrict this to only when doing ascending which I think is a bad idea. If you don't have any further suggestion I'll abandon this PR.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Sorts the k elements between minIdx and maxIdx without sorting all elements | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// Time complexity: O(n + k log k) best and average case. O(n^2) worse case. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
protected override void PartialQuickSort(int[] map, int left, int right, int minIdx, int maxIdx) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
I would use a name that emphasizes that this is an in-place sort, i.e.
buffer
is going to get mutated.