-
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
Improve perf of Enumerable.Order{Descending} for primitives #76733
Conversation
For non-floating point primitive types, stable sorts are indistinguishable from unstable sorts. They could be distinguishable if there are custom keys associated with each item, but Order{Descending} doesn't have separate keys from the elements themselves. As such, we can avoid all of the overhead associated with the int[] map Order{Descending}By creates as part of implementing the stable sort, avoid always specifying a comparer, etc. This PR does so for Order{Descending}(comparer) followed by ToArray, ToList, and foreach/GetEnumerator.
Tagging subscribers to this area: @dotnet/area-system-linq Issue DetailsFor non-floating point primitive types, stable sorts are indistinguishable from unstable sorts. They could be distinguishable if there are custom keys associated with each item, but Order{Descending} doesn't have separate keys from the elements themselves. As such, we can avoid all of the overhead associated with the int[] map Order{Descending}By creates as part of implementing the stable sort, avoid always specifying a comparer, etc. This PR does so for Order{Descending}(comparer) followed by ToArray, ToList, and foreach/GetEnumerator. [Params(10, 1_000, 1_000_000)]
public int Size { get; set; }
[Params(false, true)]
public bool CustomComparer { get; set; }
private IEnumerable<int> _source;
private IComparer<int> _comparer;
[GlobalSetup]
public void Setup()
{
_source = Enumerable.Range(0, Size).Reverse();
_comparer = CustomComparer ? Comparer<int>.Create((a, b) => a.CompareTo(b)) : null;
}
[Benchmark]
public int[] OrderToArray() => _source.Order(_comparer).ToArray();
[Benchmark]
public List<int> OrderToList() => _source.Order(_comparer).ToList();
[Benchmark]
public int[] OrderDescendingToArray() => _source.OrderDescending(_comparer).ToArray();
[Benchmark]
public List<int> OrderDescendingToList() => _source.OrderDescending(_comparer).ToList();
|
src/libraries/System.Linq/src/System/Linq/OrderedEnumerable.SpeedOpt.cs
Outdated
Show resolved
Hide resolved
Hm, what if the custom comparer ignores some bits? Then, even integers become distinguishable. In the extreme, the comparer could compare just the lowest one bit. This effectively encodes key and value in the integer together. |
What's an example where that would observably impact the resulting ordering, from an unstable vs stable perspective? |
I'll contrive a radically simple one. Let's say, the comparer always returns zero. Then: Input: [0, 1] Or am I confused? |
Ah, that's fair. I was thinking of it from the perspective of the comparer saying that two identical values weren't, but it's true that if a comparer said that two non-identical values were the same that could result in observable differences in a stable vs unstable ordering. |
For non-floating point primitive types, stable sorts are indistinguishable from unstable sorts. They could be distinguishable if there are custom keys associated with each item, but Order{Descending} doesn't have separate keys from the elements themselves. As such, we can avoid all of the overhead associated with the int[] map Order{Descending}By creates as part of implementing the stable sort, avoid always specifying a comparer, etc. This PR does so for Order{Descending}(comparer) followed by ToArray, ToList, and foreach/GetEnumerator.