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

Add Span.Sort, and make Array.Sort span-based #27700

Merged
merged 2 commits into from
Nov 6, 2019

Conversation

stephentoub
Copy link
Member

Replaces #24419 from @nietras.

The first commit is @nietras', rebased on top of master.

The second commit cleans up a few things, including removing the native TrySZSort entirely in favor of just using the managed implementation, and passing around only spans rather than spans + lo index + hi index.

Fixes https://github.com/dotnet/coreclr/issues/27683
Contributes to https://github.com/dotnet/corefx/issues/15329

cc: @jkotas, @nietras

I ran the dotnet/performance Array.Sort benchmarks (old is master, new is this PR):

Type Method Toolchain Size Mean Error StdDev Median Min Max Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
Sort Array \new\corerun.exe 512 8.657 us 0.8237 us 0.8814 us 8.393 us 7.790 us 11.029 us 0.85 0.10 - - - -
Sort Array \old\corerun.exe 512 10.215 us 0.9868 us 1.0558 us 9.811 us 9.086 us 12.327 us 1.00 0.00 - - - -
Sort Array \new\corerun.exe 512 3.898 us 0.1021 us 0.1093 us 3.899 us 3.753 us 4.152 us 0.82 0.03 - - - -
Sort Array \old\corerun.exe 512 4.772 us 0.1108 us 0.1138 us 4.772 us 4.605 us 4.984 us 1.00 0.00 - - - -
Sort Array \new\corerun.exe 512 31.986 us 0.1564 us 0.1386 us 31.980 us 31.778 us 32.264 us 0.98 0.00 - - - -
Sort Array \old\corerun.exe 512 32.773 us 0.0791 us 0.0701 us 32.773 us 32.648 us 32.925 us 1.00 0.00 - - - -
Sort Array \new\corerun.exe 512 5.014 us 1.6981 us 1.8874 us 3.986 us 3.901 us 8.970 us 1.07 0.25 - - - -
Sort Array \old\corerun.exe 512 4.570 us 0.6052 us 0.6969 us 4.059 us 3.955 us 5.955 us 1.00 0.00 - - - -
Sort Array \new\corerun.exe 512 287.612 us 0.9120 us 0.7120 us 287.485 us 286.617 us 288.895 us 1.03 0.00 - - - -
Sort Array \old\corerun.exe 512 280.042 us 0.8725 us 0.7286 us 280.188 us 278.930 us 281.135 us 1.00 0.00 - - - -
Sort Array_ComparerClass \new\corerun.exe 512 28.315 us 0.7971 us 0.8859 us 27.932 us 27.380 us 30.100 us 0.99 0.03 - - - 64 B
Sort Array_ComparerClass \old\corerun.exe 512 28.458 us 0.3567 us 0.3336 us 28.391 us 28.105 us 29.032 us 1.00 0.00 - - - 64 B
Sort Array_ComparerClass \new\corerun.exe 512 19.676 us 0.2118 us 0.1981 us 19.624 us 19.480 us 20.077 us 0.99 0.01 - - - 64 B
Sort Array_ComparerClass \old\corerun.exe 512 19.832 us 0.0548 us 0.0458 us 19.815 us 19.761 us 19.917 us 1.00 0.00 - - - 64 B
Sort Array_ComparerClass \new\corerun.exe 512 39.608 us 0.1484 us 0.1388 us 39.571 us 39.447 us 39.869 us 1.05 0.00 - - - 64 B
Sort Array_ComparerClass \old\corerun.exe 512 37.876 us 0.0922 us 0.0720 us 37.866 us 37.759 us 37.996 us 1.00 0.00 - - - 64 B
Sort Array_ComparerClass \new\corerun.exe 512 18.892 us 0.0575 us 0.0538 us 18.886 us 18.827 us 19.008 us 0.88 0.00 - - - 64 B
Sort Array_ComparerClass \old\corerun.exe 512 21.416 us 0.0671 us 0.0628 us 21.414 us 21.314 us 21.550 us 1.00 0.00 - - - 64 B
Sort Array_ComparerClass \new\corerun.exe 512 269.803 us 3.3385 us 3.1228 us 270.197 us 265.775 us 275.033 us 0.86 0.01 - - - 64 B
Sort Array_ComparerClass \old\corerun.exe 512 315.011 us 1.4353 us 1.2723 us 315.079 us 312.248 us 317.227 us 1.00 0.00 - - - 64 B
Sort Array_ComparerStruct \new\corerun.exe 512 31.203 us 0.1804 us 0.1687 us 31.219 us 30.899 us 31.422 us 0.96 0.01 - - - 88 B
Sort Array_ComparerStruct \old\corerun.exe 512 32.334 us 0.2771 us 0.2456 us 32.297 us 32.012 us 32.902 us 1.00 0.00 - - - 88 B
Sort Array_ComparerStruct \new\corerun.exe 512 23.737 us 0.1534 us 0.1435 us 23.676 us 23.596 us 24.077 us 1.03 0.01 - - - 88 B
Sort Array_ComparerStruct \old\corerun.exe 512 23.099 us 0.0647 us 0.0605 us 23.086 us 23.014 us 23.230 us 1.00 0.00 - - - 88 B
Sort Array_ComparerStruct \new\corerun.exe 512 40.157 us 0.1432 us 0.1270 us 40.106 us 40.043 us 40.464 us 0.99 0.00 - - - 88 B
Sort Array_ComparerStruct \old\corerun.exe 512 40.561 us 0.1432 us 0.1339 us 40.512 us 40.419 us 40.925 us 1.00 0.00 - - - 88 B
Sort Array_ComparerStruct \new\corerun.exe 512 23.095 us 0.0537 us 0.0476 us 23.088 us 23.006 us 23.185 us 0.93 0.00 - - - 88 B
Sort Array_ComparerStruct \old\corerun.exe 512 24.750 us 0.0464 us 0.0411 us 24.751 us 24.641 us 24.822 us 1.00 0.00 - - - 88 B
Sort Array_ComparerStruct \new\corerun.exe 512 289.899 us 1.1267 us 0.9408 us 289.953 us 288.531 us 291.185 us 1.06 0.01 - - - 88 B
Sort Array_ComparerStruct \old\corerun.exe 512 274.180 us 1.5219 us 1.2709 us 274.031 us 272.874 us 276.486 us 1.00 0.00 - - - 88 B
Sort Array_Comparison \new\corerun.exe 512 27.693 us 0.3704 us 0.3283 us 27.576 us 27.335 us 28.225 us 0.97 0.02 - - - -
Sort Array_Comparison \old\corerun.exe 512 28.425 us 0.4428 us 0.3925 us 28.362 us 27.895 us 29.052 us 1.00 0.00 - - - -
Sort Array_Comparison \new\corerun.exe 512 18.507 us 0.1165 us 0.0972 us 18.508 us 18.390 us 18.732 us 1.00 0.01 - - - -
Sort Array_Comparison \old\corerun.exe 512 18.542 us 0.0847 us 0.0661 us 18.528 us 18.444 us 18.689 us 1.00 0.00 - - - -
Sort Array_Comparison \new\corerun.exe 512 38.252 us 0.1348 us 0.1195 us 38.251 us 38.015 us 38.461 us 1.02 0.01 - - - -
Sort Array_Comparison \old\corerun.exe 512 37.534 us 0.1877 us 0.1756 us 37.528 us 37.295 us 37.861 us 1.00 0.00 - - - -
Sort Array_Comparison \new\corerun.exe 512 19.358 us 0.1053 us 0.0934 us 19.344 us 19.255 us 19.582 us 0.94 0.01 - - - -
Sort Array_Comparison \old\corerun.exe 512 20.618 us 0.0984 us 0.0920 us 20.578 us 20.522 us 20.812 us 1.00 0.00 - - - -
Sort Array_Comparison \new\corerun.exe 512 275.570 us 1.2051 us 1.0683 us 275.188 us 274.710 us 277.900 us 0.91 0.00 - - - -
Sort Array_Comparison \old\corerun.exe 512 303.104 us 1.0831 us 0.9045 us 303.470 us 301.982 us 304.219 us 1.00 0.00 - - - -

I also ran a few additional benchmarks to try to measure impact on small arrays where other overheads might show up:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Running;
using System;
using System.Linq;

[MemoryDiagnoser]
public class Program
{
    static void Main(string[] args) => BenchmarkSwitcher.FromAssemblies(new[] { typeof(Program).Assembly }).Run(args);

    [Params(1, 5, 10, 50)]
    public int Length { get; set; }

    private int[] _intArray;

    private static bool s_direction = false;

    private IntStruct[] _intStructArray;

    private readonly struct IntStruct : IComparable<IntStruct>
    {
        private readonly int _value;
        public IntStruct(int value) => _value = value;
        public int CompareTo(IntStruct other) => s_direction ? _value.CompareTo(other._value) : other._value.CompareTo(_value);
    }

    [GlobalSetup]
    public void Setup()
    {
        _intArray = Enumerable.Range(0, Length).ToArray();
        _intStructArray = Enumerable.Range(0, Length).Reverse().Select(i => new IntStruct(i)).ToArray();
    }

    [Benchmark]
    public void AlreadySorted() => Array.Sort(_intArray);

    [Benchmark]
    public void AlreadySorted_NonGeneric() => Array.Sort((Array)_intArray);

    [Benchmark]
    public void Inverse()
    {
        s_direction = !s_direction;
        Array.Sort(_intStructArray);
    }

    [Benchmark]
    public void Inverse_NonGeneric()
    {
        s_direction = !s_direction;
        Array.Sort(_intStructArray);
    }
}
Method Toolchain Length Mean Error StdDev Ratio RatioSD
AlreadySorted \new\corerun.exe 1 0.9878 ns 0.0083 ns 0.0073 ns 0.58 0.00
AlreadySorted \old\corerun.exe 1 1.7149 ns 0.0022 ns 0.0020 ns 1.00 0.00
AlreadySorted_NonGeneric \new\corerun.exe 1 12.8182 ns 0.0562 ns 0.0526 ns 1.98 0.01
AlreadySorted_NonGeneric \old\corerun.exe 1 6.4623 ns 0.0116 ns 0.0109 ns 1.00 0.00
Inverse \new\corerun.exe 1 0.9696 ns 0.0045 ns 0.0040 ns 0.43 0.00
Inverse \old\corerun.exe 1 2.2722 ns 0.0014 ns 0.0011 ns 1.00 0.00
Inverse_NonGeneric \new\corerun.exe 1 0.8297 ns 0.0022 ns 0.0019 ns 0.45 0.00
Inverse_NonGeneric \old\corerun.exe 1 1.8548 ns 0.0028 ns 0.0026 ns 1.00 0.00
AlreadySorted \new\corerun.exe 5 16.2019 ns 0.0233 ns 0.0206 ns 0.64 0.00
AlreadySorted \old\corerun.exe 5 25.2719 ns 0.0340 ns 0.0301 ns 1.00 0.00
AlreadySorted_NonGeneric \new\corerun.exe 5 51.9199 ns 0.0690 ns 0.0645 ns 1.32 0.00
AlreadySorted_NonGeneric \old\corerun.exe 5 39.3405 ns 0.0528 ns 0.0468 ns 1.00 0.00
Inverse \new\corerun.exe 5 28.9344 ns 0.0859 ns 0.0803 ns 0.86 0.00
Inverse \old\corerun.exe 5 33.6274 ns 0.0996 ns 0.0931 ns 1.00 0.00
Inverse_NonGeneric \new\corerun.exe 5 28.9753 ns 0.0835 ns 0.0652 ns 0.86 0.00
Inverse_NonGeneric \old\corerun.exe 5 33.8191 ns 0.0875 ns 0.0776 ns 1.00 0.00
AlreadySorted \new\corerun.exe 10 21.9842 ns 0.0601 ns 0.0532 ns 0.58 0.00
AlreadySorted \old\corerun.exe 10 37.9305 ns 0.0944 ns 0.0737 ns 1.00 0.00
AlreadySorted_NonGeneric \new\corerun.exe 10 57.3741 ns 0.1686 ns 0.1577 ns 1.10 0.00
AlreadySorted_NonGeneric \old\corerun.exe 10 52.2404 ns 0.0567 ns 0.0473 ns 1.00 0.00
Inverse \new\corerun.exe 10 90.1305 ns 0.1498 ns 0.1328 ns 0.95 0.00
Inverse \old\corerun.exe 10 94.6786 ns 0.1084 ns 0.0905 ns 1.00 0.00
Inverse_NonGeneric \new\corerun.exe 10 90.3232 ns 0.5252 ns 0.4656 ns 0.95 0.01
Inverse_NonGeneric \old\corerun.exe 10 95.1975 ns 0.1188 ns 0.0928 ns 1.00 0.00
AlreadySorted \new\corerun.exe 50 131.9916 ns 0.3596 ns 0.3187 ns 0.91 0.00
AlreadySorted \old\corerun.exe 50 145.2568 ns 0.9565 ns 0.8947 ns 1.00 0.00
AlreadySorted_NonGeneric \new\corerun.exe 50 172.3455 ns 0.5048 ns 0.4475 ns 1.08 0.00
AlreadySorted_NonGeneric \old\corerun.exe 50 160.1318 ns 0.6221 ns 0.5515 ns 1.00 0.00
Inverse \new\corerun.exe 50 390.3818 ns 0.9525 ns 0.8910 ns 0.95 0.02
Inverse \old\corerun.exe 50 412.0761 ns 8.0010 ns 8.5609 ns 1.00 0.00
Inverse_NonGeneric \new\corerun.exe 50 394.3612 ns 1.6422 ns 1.5361 ns 0.97 0.00
Inverse_NonGeneric \old\corerun.exe 50 408.4902 ns 2.0410 ns 1.7043 ns 1.00 0.00

Non-generic takes a small hit in overhead, but the numbers here are also miniscule, and the non-generic methods are generally not used by anyone who cares about this level of perf.

nietras and others added 2 commits November 5, 2019 21:23
Shares existing Array-based implementation by changing that implementation to be span based.
- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.
Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome!

@jkotas jkotas merged commit 50d73a2 into dotnet:master Nov 6, 2019
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corefx that referenced this pull request Nov 6, 2019
* Add Span<T>.Sort

Shares existing Array-based implementation by changing that implementation to be span based.

* Additional work to enable span-based sorts

- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/corert that referenced this pull request Nov 6, 2019
* Add Span<T>.Sort

Shares existing Array-based implementation by changing that implementation to be span based.

* Additional work to enable span-based sorts

- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Dotnet-GitSync-Bot pushed a commit to Dotnet-GitSync-Bot/mono that referenced this pull request Nov 6, 2019
* Add Span<T>.Sort

Shares existing Array-based implementation by changing that implementation to be span based.

* Additional work to enable span-based sorts

- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
@nietras
Copy link

nietras commented Nov 6, 2019

@stephentoub great! 👍

case TypeCode.Single:
if (TryGenericSort(keys as float[], items, index, length)) return;
break;
case TypeCode.String:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was it intentional to include String here?

I believe that there are subtle differences in how the current culture is handled between the non-generic and generic default comparer, so using this path for string may change the behavior.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll just remove this case then. Thanks.

@GSPP
Copy link

GSPP commented Nov 6, 2019

It is possible that typeof(T[]) != array.GetType(). This happens when passing in, for example, (uint[])(object)new int[10]. The CLR type system allows treating an int[] as a uint[].

I wonder if there is a behavior change here if previously the native code used the runtime type of the array and now the C# code uses the type of the generic argument.

@jkotas
Copy link
Member

jkotas commented Nov 6, 2019

All these cases should work fine. The switch is done on TypeCode. It that prevents int[] to be confused with uint[].

jkotas pushed a commit to dotnet/corefx that referenced this pull request Nov 6, 2019
* Add Span<T>.Sort

Shares existing Array-based implementation by changing that implementation to be span based.

* Additional work to enable span-based sorts

- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
@stephentoub stephentoub deleted the spansort branch November 6, 2019 11:15
marek-safar pushed a commit to mono/mono that referenced this pull request Nov 6, 2019
* Add Span<T>.Sort

Shares existing Array-based implementation by changing that implementation to be span based.

* Additional work to enable span-based sorts

- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
@adamsitnik
Copy link
Member

@nietras @stephentoub great work!

/cc @damageboy who is working on a vectorized version of Array.Sort ;)

jkotas pushed a commit to dotnet/corert that referenced this pull request Nov 6, 2019
* Add Span<T>.Sort

Shares existing Array-based implementation by changing that implementation to be span based.

* Additional work to enable span-based sorts

- Cleans up changes from the previous commit, e.g. corrects nullable annotations, removes TODOs, using GetRawSzArrayData instead of GetRawArrayData, etc.
- Passes spans around rather than spans+lo+hi.
- Deletes the native TrySZSort, preferring to use the managed implementation in all cases.

Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
@damageboy
Copy link

Definitely nice to have this back it managed land, this will make special casing my stuff much more friendly :)

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.

Long GC pause times caused by Array.Sort of primitive array
6 participants