You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何使用自定义比较器的OrderBy性能远低于默认实现?

自定义比较器排序性能远低于直接OrderBy的原因分析

我原本以为两种排序逻辑本质相同,性能应该相近,但测试结果显示差异极大。想请教为什么使用自定义IComparer<Row>的排序版本(Sort2)性能远低于直接通过OrderBy指定Key的版本(Sort1)?难道是因为自定义比较器版本需要调用Compare方法和double.CompareTo,而Sort1仅调用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>();
    }
}

性能测试结果

初始测试数据

MethodMeanErrorStdDev
Sort1111.6 ms1.46 ms1.79 ms
Sort2343.5 ms4.01 ms7.42 ms

补充Sort3后的测试数据

MethodMeanErrorStdDevAllocated
Sort1112.0 ms0.90 ms0.85 ms13.35 MB
Sort2344.4 ms2.27 ms2.12 ms13.35 MB
Sort3138.7 ms0.64 ms0.57 ms17.17 MB

核心原因分析

两者性能差距的本质在于LINQ OrderBy不同重载的实现逻辑差异:

  • Sort1的OrderBy(x => x.Values[8])实现逻辑
    这个重载接收KeySelector委托,会先遍历所有Row元素,一次性提取并缓存所有需要排序的Key(即x.Values[8]的double值),存储到临时数组中。后续排序过程中,仅需直接比较临时数组里的预存Key,无需再访问Row对象的Values列表。这种方式下每个Row的目标值只被读取一次,排序时的比较操作仅涉及简单的double.CompareTo,开销极低。

  • Sort2的OrderBy(e => e, comparer)实现逻辑
    这个重载接收IComparer<Row>,不会预提取或缓存排序Key。每次比较两个Row对象时,都要调用自定义比较器的Compare方法,内部需要两次访问List<double>的索引,再调用double.CompareTo完成比较。排序的比较次数是O(n log n)级别(50万元素总比较次数约950万次),每次比较都重复上述操作,额外的索引访问和方法调用开销累加后,总耗时达到Sort1的3倍左右。

  • 关于Sort3的补充
    Sort3耗时介于两者之间,结合内存分配更高的特点,推测它是手动预提取Key(比如将Row和对应Key封装成Tuple)后再排序,这种方式避免了重复访问Values,但额外的对象封装带来少量开销,因此性能略逊于Sort1,但远优于Sort2。

内容的提问来源于stack exchange,提问作者volvic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 07:15:29