在下面的例子中,有人能解释一下为什么使用自定义比较器的版本比使用其他方法的版本慢得多吗?

我以为他们会很相似,因为他们实际上在做同样的事情?

是因为定制比较器版本必须调用两个方法吗?先进行比较,然后再进行加倍.比较对象?其中,我猜第一个版本只调用了Double.CompareTo?但这真的能解释这么大的差异吗?

public class Program
{
    public class ColumnComparer : IComparer<Row>
    {
        public ColumnComparer(int column)
        {
            Column = column;
        }

        public int Column { get; }

        public int Compare(Row x, Row y) => x.Values[Column].CompareTo(y.Values[Column]);
    }

    public class Row
    {
        public int Index { get; }

        public List<double> Values { get; } = new();

        public Row(int index)
        {
            Index = index;
        }
    }

    public class Sort
    {
        private readonly List<Row> _rows;

        public Sort()
        {
            _rows = new List<Row>();
            var r = new Random(0);

            for (int i = 0; i < 500000; ++i)
            {
                var row = new Row(i);
                for (int j = 0; j < 32; ++j)
                {
                    row.Values.Add(r.Next(0, 1000));
                }

                _rows.Add(row);
            }
        }

        [Benchmark]
        public void Sort1()
        {
            _rows.OrderBy(x => x.Values[8]).ToArray();
        }

        [Benchmark]
        public void Sort2()
        {
            var comparer = new ColumnComparer(8);
            var sort2 = _rows.OrderBy(e => e, comparer).ToArray();
        }
    }

    static void Main(string[] args)
    {
        BenchmarkRunner.Run<Sort>();
    }
}

编辑后的内容包括绩效衡量:

方法 均值 错误 标准开发
排序1 111.6毫秒 1.46毫秒 1.79毫秒
排序2 343.5毫秒 4.01毫秒 7.42毫秒

编辑:

新增指标包括Sort3(@dbc)

方法 均值 错误 标准开发 已分配
排序1 112.0毫秒 0.90毫秒 0.85ms 13.35 MB
排序2 344.4毫秒 2.27毫秒 2.12毫秒 13.35 MB
排序3 138.7毫秒 0.64毫秒 0.57毫秒 17.17 MB

推荐答案

自定义排序比默认排序慢3倍也就不足为奇了.当LINQ判断时

_rows.OrderBy(x => x.Values[8]).ToArray();

EnumerableSorter<TElement, TKey>通过调用keySelector方法once per item来构造double []? _keys;的数组(因为在本例中TKeydouble).然后,它使用default double comparison(通过GenericComparer<double>.CompareTo())对该数组进行就地排序,这将被调用大约107次.由于没有任何东西被装箱或取消装箱,这应该是相当快的.

相反,当您这样做时:

_rows.OrderBy(e => e, comparer).ToArray()

EnumerableSorter<TElement, TKey>将构造一个Row []数组,然后103次调用您的自定义排序方法x.Values[Column].CompareTo(y.Values[Column]).由于此方法比默认的双重比较方法复杂得多,并且涉及多个指针取消引用和列表查找,因此花费更长的时间也就不足为奇了.

这是一个通用规则的示例,当使用复杂的排序方法对大型集合进行排序时,性能不够好,it can pay to precompute as much of the work done by the comparer as possible in the key selector.这正是您使用x => x.Values[8]键 Select 器所做的事情.

如果您需要在排序完成后访问预计算出的键值,则可以在排序前将其投影到ValueTuple<TElement, TKey>,例如:

[Benchmark]
public void Sort3()
{
    var sort3 = _rows
        .Select(row => (row, value : row.Values[8])) // Precompute the value
        .OrderBy(p => p.value)                       // Compare the value
        .Select(p => p.row).ToArray();               // And extract the original row.
}

我目前无法访问BenchmarkDotNet,但演示小提琴here显示,在对45,000行的列表进行排序时,Sort3()仅比Sort1()慢15%-25%:

Average time per repetition for 100 reps of Sort1: 12.2888 ms.
Average time per repetition for 100 reps of Sort2: 37.8331 ms (207.87% slower).
Average time per repetition for 100 reps of Sort3: 14.6479 ms (19.20% slower).

Csharp相关问答推荐

ListaryImportProperty的默认DllImportSearchPathsProperty行为

将修剪声明放入LINQ中

ASP.NET核心结果.文件vs结果.流

EF Core在请求列表时忽略列,但在按ID获取时包含

使用LINQ to XML获取元素值列表是不起作用的

. NET 8控制台应用程序DI错误无法解析Microsoft. Extension. Logging. ILoggerFactory类型的服务'''

Nuget包Serilog.Sinks.AwsCloudwatch引发TypeLoadExceptions,因为父类型是密封的

共享暂存/生产环境中Azure事件中心的建议配置

try 使用C#ASP.NET收集WMI信息时访问被拒绝,但在PowerShell中工作

调用Task.Run()与DoSomethingAsync()有什么不同?

在不添加不必要的尾随零的情况下本地化浮点型?

DbContext-传递自定义配置选项

WPF动态设置弹出窗口水平偏移

匿名类型的AbstractValidator

C#使用相同内存的多个数组

ASP.NET Core 8 Web API:如何添加版本控制?

C#;AvaloniaUI;MVVM;当另一个窗口上的按钮被单击时,如何更新视图图像源?

SqlException:无法打开数据库.升级到Dotnet 8后-数据库兼容性版本-非EFCore兼容性级别

如何对正方形格线进行对角分组

C#中的逻辑运算符用作单词';is';and';and';