Skip to content

Conversation

@stephentoub
Copy link
Member

Enumerable.OrderBy{Descending} buffers the input into an array. It then allocates a TKey[] from that input to store the keys, and allocates an int[] that's what's actually sorted based on those keys. The enumerator uses that sorted int[] then to decide what element from the buffered input to yield next. In the case of the new Order and OrderDescending methods, though, that TKey[] isn't required; it's just a copy of the buffered input, so we can avoid it.

Method Toolchain Length Mean Ratio Allocated Alloc Ratio
Order \main\corerun.exe 5 625.1 ns 1.00 448 B 1.00
Order \pr\corerun.exe 5 596.0 ns 0.95 384 B 0.86
OrderBy \main\corerun.exe 5 617.5 ns 1.00 448 B 1.00
OrderBy \pr\corerun.exe 5 625.3 ns 1.01 448 B 1.00
Order \main\corerun.exe 50 12,843.7 ns 1.00 1344 B 1.00
Order \pr\corerun.exe 50 10,852.7 ns 0.85 920 B 0.68
OrderBy \main\corerun.exe 50 10,693.7 ns 1.00 1344 B 1.00
OrderBy \pr\corerun.exe 50 10,555.1 ns 0.98 1344 B 1.00
Order \main\corerun.exe 500 216,455.8 ns 1.00 10344 B 1.00
Order \pr\corerun.exe 500 216,948.5 ns 1.00 6320 B 0.61
OrderBy \main\corerun.exe 500 237,440.7 ns 1.00 10344 B 1.00
OrderBy \pr\corerun.exe 500 216,897.4 ns 0.91 10344 B 1.00
[Params(5, 50, 500)]
public int Length { get; set; }

private string[] _arr;

private static readonly Func<IEnumerable<string>, IEnumerable<string>> s_order = typeof(Enumerable).GetMethod("Order", new[] { typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) })!.MakeGenericMethod(typeof(string)).CreateDelegate<Func<IEnumerable<string>, IEnumerable<string>>>();
private static readonly Func<IEnumerable<string>, Func<string, string>, IEnumerable<string>> s_orderBy = typeof(Enumerable).GetMethod("OrderBy", new[] { typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), typeof(Func<,>).MakeGenericType(Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(1)) })!.MakeGenericMethod(typeof(string), typeof(string)).CreateDelegate<Func<IEnumerable<string>, Func<string, string>, IEnumerable<string>>>();

[GlobalSetup]
public void Setup() => _arr = Enumerable.Range(1, Length).Select(i => i.ToString()).Reverse().ToArray();

[Benchmark]
public void Order()
{
    foreach (string item in s_order(_arr)) { }
}

[Benchmark]
public void OrderBy()
{
    foreach (string item in s_orderBy(_arr, i => i)) { }
}

Enumerable.OrderBy{Descending} buffers the input into an array.  It then allocates a TKey[] from that input to store the keys, and allocates an int[] that's what's actually sorted based on those keys.  The enumerator uses that sorted int[] then to decide what element from the buffered input to yield next.  In the case of the new Order and OrderDescending methods, though, that TKey[] isn't required; it's just a copy of the buffered input, so we can avoid it.
@ghost ghost added the area-System.Linq label Jul 1, 2022
@ghost ghost assigned stephentoub Jul 1, 2022
@ghost
Copy link

ghost commented Jul 1, 2022

Tagging subscribers to this area: @dotnet/area-system-linq
See info in area-owners.md if you want to be subscribed.

Issue Details

Enumerable.OrderBy{Descending} buffers the input into an array. It then allocates a TKey[] from that input to store the keys, and allocates an int[] that's what's actually sorted based on those keys. The enumerator uses that sorted int[] then to decide what element from the buffered input to yield next. In the case of the new Order and OrderDescending methods, though, that TKey[] isn't required; it's just a copy of the buffered input, so we can avoid it.

Method Toolchain Length Mean Ratio Allocated Alloc Ratio
Order \main\corerun.exe 5 625.1 ns 1.00 448 B 1.00
Order \pr\corerun.exe 5 596.0 ns 0.95 384 B 0.86
OrderBy \main\corerun.exe 5 617.5 ns 1.00 448 B 1.00
OrderBy \pr\corerun.exe 5 625.3 ns 1.01 448 B 1.00
Order \main\corerun.exe 50 12,843.7 ns 1.00 1344 B 1.00
Order \pr\corerun.exe 50 10,852.7 ns 0.85 920 B 0.68
OrderBy \main\corerun.exe 50 10,693.7 ns 1.00 1344 B 1.00
OrderBy \pr\corerun.exe 50 10,555.1 ns 0.98 1344 B 1.00
Order \main\corerun.exe 500 216,455.8 ns 1.00 10344 B 1.00
Order \pr\corerun.exe 500 216,948.5 ns 1.00 6320 B 0.61
OrderBy \main\corerun.exe 500 237,440.7 ns 1.00 10344 B 1.00
OrderBy \pr\corerun.exe 500 216,897.4 ns 0.91 10344 B 1.00
[Params(5, 50, 500)]
public int Length { get; set; }

private string[] _arr;

private static readonly Func<IEnumerable<string>, IEnumerable<string>> s_order = typeof(Enumerable).GetMethod("Order", new[] { typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)) })!.MakeGenericMethod(typeof(string)).CreateDelegate<Func<IEnumerable<string>, IEnumerable<string>>>();
private static readonly Func<IEnumerable<string>, Func<string, string>, IEnumerable<string>> s_orderBy = typeof(Enumerable).GetMethod("OrderBy", new[] { typeof(IEnumerable<>).MakeGenericType(Type.MakeGenericMethodParameter(0)), typeof(Func<,>).MakeGenericType(Type.MakeGenericMethodParameter(0), Type.MakeGenericMethodParameter(1)) })!.MakeGenericMethod(typeof(string), typeof(string)).CreateDelegate<Func<IEnumerable<string>, Func<string, string>, IEnumerable<string>>>();

[GlobalSetup]
public void Setup() => _arr = Enumerable.Range(1, Length).Select(i => i.ToString()).Reverse().ToArray();

[Benchmark]
public void Order()
{
    foreach (string item in s_order(_arr)) { }
}

[Benchmark]
public void OrderBy()
{
    foreach (string item in s_orderBy(_arr, i => i)) { }
}
Author: stephentoub
Assignees: -
Labels:

area-System.Linq

Milestone: -

@stephentoub stephentoub merged commit 63e9379 into dotnet:main Jul 7, 2022
@stephentoub stephentoub deleted the orderperf branch July 7, 2022 11:33
@ghost ghost locked as resolved and limited conversation to collaborators Aug 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants