C# Sort 和 OrderBy 比较

Posted

技术标签:

【中文标题】C# Sort 和 OrderBy 比较【英文标题】:C# Sort and OrderBy comparison 【发布时间】:2010-12-22 09:44:56 【问题描述】:

我可以使用 Sort 或 OrderBy 对列表进行排序。哪个更快?都在做同样的事情 算法?

List<Person> persons = new List<Person>();
persons.Add(new Person("P005", "Janson"));
persons.Add(new Person("P002", "Aravind"));
persons.Add(new Person("P007", "Kazhal"));

1.

persons.Sort((p1,p2)=>string.Compare(p1.Name,p2.Name,true));

2.

var query = persons.OrderBy(n => n.Name, new NameComparer());

class NameComparer : IComparer<string>

    public int Compare(string x,string y)
    
      return  string.Compare(x, y, true);
    

【问题讨论】:

我不敢相信没有一个答案提到这一点,但最大的区别在于:OrderBy 是对 Array 或 List 进行排序的副本,而 Sort 实际上是对其进行排序。 正如标题所说的比较,我想补充一点,OrderBy 是稳定的,并且排序是稳定的,最多 16 个元素,因为如果元素多于 16 个元素,则使用插入排序,然后它会切换到其他不稳定的算法编辑:稳定意味着保持具有相同键的元素的相对顺序。 @PRMan 不,OrderBy 创建了一个惰性可枚举。只有在返回的枚举上调用诸如 ToList 之类的方法时,才会得到排序后的副本。 @Stewart,您不认为 System.Core/System/Linq/Enumerable.cs 中缓冲区中的 Array.Copy 或 Collection.Copy 到 TElement[] 是副本吗?而且,如果您在 IEnumerable 上调用 ToList,您可能会立即在内存中同时拥有 3 个副本。这对于非常大的数组来说是一个问题,这是我的观点的一部分。此外,如果您多次需要相同的排序顺序,则调用就地排序一次比重复排序列表更有效,因为它是永久性的。 @PRMan 哦,你的意思是内部构建了一个排序的副本。这仍然是不准确的,因为 OrderBy 不会创建副本 - 据我所知,这是由 GetEnumerator 方法在您实际开始遍历集合时完成的。我刚刚尝试单步执行我的代码,发现从 LINQ 表达式填充变量的代码几乎可以立即运行,但是当您进入 foreach 循环时,它会花时间对其进行排序。我想当我有更多时间时,我应该花一些时间来弄清楚它是如何在幕后工作的。 【参考方案1】:

我只想补充一点,orderby 更有用。

为什么?因为我可以这样做:

Dim thisAccountBalances = account.DictOfBalances.Values.ToList
thisAccountBalances.ForEach(Sub(x) x.computeBalanceOtherFactors())
thisAccountBalances=thisAccountBalances.OrderBy(Function(x) x.TotalBalance).tolist
listOfBalances.AddRange(thisAccountBalances)

为什么要复杂的比较器?只需根据字段进行排序。这里我是根据 TotalBalance 进行排序的。

很简单。

我不能用排序来做到这一点。我想知道为什么。使用 orderBy 就可以了。

至于速度,它总是 O(n)。

【讨论】:

问题:您的答案中的 O(n) 时间(我假设)是指 OrderBy 还是 Comparer?我不认为快速排序可以达到 O(N) 时间。【参考方案2】:

简而言之:

列表/数组排序():

不稳定的排序。 就地完成。 使用 Introsort/Quicksort。 自定义比较是通过提供一个比较器来完成的。如果比较代价高昂,它可能会比 OrderBy() 慢(允许使用键,见下文)。

OrderBy/ThenBy():

稳定排序。 不在原位。 使用快速排序。快速排序不是一种稳定的排序。诀窍是:排序时,如果两个元素具有相同的键,它会比较它们的初始顺序(在排序之前已存储)。 允许使用键(使用 lambda)根据元素的值对元素进行排序(例如:x =&gt; x.Id)。在排序之前首先提取所有键。与使用 Sort() 和自定义比较器相比,这可能会产生更好的性能。

来源: MDSN、reference source 和 dotnet/coreclr 存储库 (GitHub)。

上面列出的一些语句基于当前的 .NET 框架实现 (4.7.2)。将来可能会改变。

【讨论】:

【参考方案3】:

我认为注意SortOrderBy 之间的另一个区别很重要:

假设存在Person.CalculateSalary() 方法,需要花费大量时间;甚至可能超过对大列表进行排序的操作。

比较

// Option 1
persons.Sort((p1, p2) => Compare(p1.CalculateSalary(), p2.CalculateSalary()));
// Option 2
var query = persons.OrderBy(p => p.CalculateSalary()); 

选项 2 可能具有更优越的性能,因为它只调用 CalculateSalary 方法 n 次,而 Sort 选项可能调用 CalculateSalary 最多 @ 987654321@ 次,取决于排序算法的成功与否。

【讨论】:

这是真的,尽管有解决该问题的方法,即将数据保存在数组中并使用 Array.Sort 重载,该重载采用两个数组,一个是键,另一个是值.在填充键数组时,您将调用CalculateSalary n 次。这显然不如使用 OrderBy 方便。【参考方案4】:

Darin Dimitrov 的回答表明,OrderBy 在面对已经排序的输入时比List.Sort 稍快。我修改了他的代码,让它反复对未排序的数据进行排序,OrderBy 在大多数情况下会稍微慢一些。

此外,OrderBy 测试使用ToArray 来强制枚举 Linq 枚举器,但这显然返回了与输入类型 (List&lt;Person&gt;) 不同的类型 (Person[])。因此,我使用ToList 而不是ToArray 重新运行了测试,并得到了更大的差异:

Sort: 25175ms
OrderBy: 30259ms
OrderByWithToList: 31458ms

代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

class Program

    class NameComparer : IComparer<string>
    
        public int Compare(string x, string y)
        
            return string.Compare(x, y, true);
        
    

    class Person
    
        public Person(string id, string name)
        
            Id = id;
            Name = name;
        
        public string Id  get; set; 
        public string Name  get; set; 
        public override string ToString()
        
            return Id + ": " + Name;
        
    

    private static Random randomSeed = new Random();
    public static string RandomString(int size, bool lowerCase)
    
        var sb = new StringBuilder(size);
        int start = (lowerCase) ? 97 : 65;
        for (int i = 0; i < size; i++)
        
            sb.Append((char)(26 * randomSeed.NextDouble() + start));
        
        return sb.ToString();
    

    private class PersonList : List<Person>
    
        public PersonList(IEnumerable<Person> persons)
           : base(persons)
        
        

        public PersonList()
        
        

        public override string ToString()
        
            var names = Math.Min(Count, 5);
            var builder = new StringBuilder();
            for (var i = 0; i < names; i++)
                builder.Append(this[i]).Append(", ");
            return builder.ToString();
        
    

    static void Main()
    
        var persons = new PersonList();
        for (int i = 0; i < 100000; i++)
        
            persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
         

        var unsortedPersons = new PersonList(persons);

        const int COUNT = 30;
        Stopwatch watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        
            watch.Start();
            Sort(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        
        Console.WriteLine("Sort: 0ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        
            watch.Start();
            OrderBy(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        
        Console.WriteLine("OrderBy: 0ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        
            watch.Start();
            OrderByWithToList(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        
        Console.WriteLine("OrderByWithToList: 0ms", watch.ElapsedMilliseconds);
    

    static void Sort(List<Person> list)
    
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    

    static void OrderBy(List<Person> list)
    
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    

    static void OrderByWithToList(List<Person> list)
    
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToList();
    

【讨论】:

我现在在 LinqPad 5 (.net 5) 中运行测试代码,OrderByWithToListOrderBy 花费的时间相同。【参考方案5】:

您应该计算 OrderBy 和 Sort 方法使用的算法的复杂度。 我记得 QuickSort 的复杂度为 n (log n),其中 n 是数组的长度。

我也搜索过 orderby,但即使在 msdn 库中也找不到任何信息。 如果您没有任何相同的值和仅与一个属性相关的排序,我更喜欢使用 排序()方法;如果不是,请使用 OrderBy。

【讨论】:

根据当前的 MSDN 文档,Sort 根据输入使用 3 种不同的排序算法。其中包括快速排序。关于 OrderBy() 算法的问题在这里(快速排序):***.com/questions/2792074/…【参考方案6】:

为什么不测量它:

class Program

    class NameComparer : IComparer<string>
    
        public int Compare(string x, string y)
        
            return string.Compare(x, y, true);
        
    

    class Person
    
        public Person(string id, string name)
        
            Id = id;
            Name = name;
        
        public string Id  get; set; 
        public string Name  get; set; 
    

    static void Main()
    
        List<Person> persons = new List<Person>();
        persons.Add(new Person("P005", "Janson"));
        persons.Add(new Person("P002", "Aravind"));
        persons.Add(new Person("P007", "Kazhal"));

        Sort(persons);
        OrderBy(persons);

        const int COUNT = 1000000;
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        
            Sort(persons);
        
        watch.Stop();
        Console.WriteLine("Sort: 0ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        
            OrderBy(persons);
        
        watch.Stop();
        Console.WriteLine("OrderBy: 0ms", watch.ElapsedMilliseconds);
    

    static void Sort(List<Person> list)
    
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    

    static void OrderBy(List<Person> list)
    
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    

在我的计算机上,当以发布模式编译时,此程序会打印:

Sort: 1162ms
OrderBy: 1269ms

更新:

正如@Stefan 所建议的,这里是对大列表进行较少次数排序的结果:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)

    persons.Add(new Person("P" + i.ToString(), "Janson" + i.ToString()));


Sort(persons);
OrderBy(persons);

const int COUNT = 30;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)

    Sort(persons);

watch.Stop();
Console.WriteLine("Sort: 0ms", watch.ElapsedMilliseconds);

watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)

    OrderBy(persons);

watch.Stop();
Console.WriteLine("OrderBy: 0ms", watch.ElapsedMilliseconds);

打印:

Sort: 8965ms
OrderBy: 8460ms

在这种情况下,OrderBy 的表现似乎更好。


更新2:

并使用随机名称:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)

    persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));

地点:

private static Random randomSeed = new Random();
public static string RandomString(int size, bool lowerCase)

    var sb = new StringBuilder(size);
    int start = (lowerCase) ? 97 : 65;
    for (int i = 0; i < size; i++)
    
        sb.Append((char)(26 * randomSeed.NextDouble() + start));
    
    return sb.ToString();

产量:

Sort: 8968ms
OrderBy: 8728ms

OrderBy 还是更快

【讨论】:

我认为,对一个非常小的列表(3 个项目)进行 1000000 次排序,或者对一个非常大的列表(1000000 个项目)进行几次排序是有很大不同的。两者都非常相关。在实践中,中等大小的列表(什么是中等?......现在假设 1000 个项目)是最有趣的。恕我直言,用 3 个项目对列表进行排序不是很有意义。 请注意,“更快”和“明显更快”之间存在差异。在您的上一个示例中,差异约为四分之一秒。用户会注意到吗?用户等待将近 9 秒才能得到结果是不能接受的吗?如果这两个问题的答案都是“否”,那么从性能的角度来看,您选择哪一个并不重要。 还要注意,这里的测试在开始秒表之前对列表进行排序,所以我们比较两种算法在面对排序输入时的比较。这可能与它们在未排序输入下的相对性能完全不同。 这些结果非常令人惊讶,恕我直言,考虑到LINQ 与就地List&lt;T&gt;.Sort 实现相比必须花费额外的内存。我不确定他们是否在较新的 .NET 版本中改进了这一点,但在我的机器上(i7 第三代 64 位 .NET 4.5 版本)Sort 在所有情况下都优于OrderBy。此外,通过查看OrderedEnumerable&lt;T&gt; 源代码,它似乎在最终调用 Quicksort 之前创建了 三个 额外的数组(首先是 Buffer&lt;T&gt;,然后是投影键数组,然后是索引数组)对索引数组进行排序。 ...在这一切之后,有 ToArray 调用创建了结果数组。内存操作和数组索引是非常快的操作,但我仍然找不到这些结果背后的逻辑。【参考方案7】:

不,它们不是同一个算法。对于初学者,LINQ OrderBy 被记录为stable(即,如果两个项目具有相同的Name,它们将按其原始顺序出现)。

这还取决于您是缓冲查询还是对其进行多次迭代(LINQ-to-Objects,除非您缓冲结果,否则将按照 foreach 重新排序)。

对于OrderBy 查询,我也很想使用:

OrderBy(n => n.Name, StringComparer.yourchoiceIgnoreCase);

(对于yourchoiceCurrentCultureOrdinalInvariantCulture 之一)。

List&lt;T&gt;.Sort

此方法使用 Array.Sort,它 使用快速排序算法。这 实现执行不稳定 种类;也就是说,如果两个元素是 相等,他们的顺序可能不是 保存。相反,稳定的排序 保留元素的顺序 是平等的。

Enumerable.OrderBy

此方法执行稳定排序;也就是说,如果两个元素的键相等,则保留元素的顺序。相反,不稳定的排序不会保留具有相同键的元素的顺序。 种类;也就是说,如果两个元素是 相等,他们的顺序可能不是 保存。相反,稳定的排序 保留元素的顺序 是平等的。

【讨论】:

如果您使用 .NET Reflector 或 ILSpy 破解 Enumerable.OrderBy 并深入了解其内部实现,您可以看到 OrderBy 排序算法是 QuickSort 的一种变体,可以进行稳定排序。 (参见System.Linq.EnumerableSorter&lt;TElement&gt;。)因此,Array.SortEnumerable.OrderBy 都可以预期有 O(N log N) 执行时间,其中 N 是数字集合中的元素。 @Marc 我不太明白如果两个元素相等并且不保留它们的顺序会有什么区别。对于原始数据类型来说,这当然不是问题。但是,即使对于引用类型,如果我要排序,名字 Marc Gravell 的人出现在另一个名字为 Marc Gravell 的人之前(例如 :))又有什么关系呢?我不是在质疑你的答案/知识,而是在寻找这种场景的应用。 @Mukus 假设您按名称(或实际上按出生日期)对公司通讯录进行排序 - 不可避免地会有重复。最终的问题是:他们会发生什么?子订单定义了吗?

以上是关于C# Sort 和 OrderBy 比较的主要内容,如果未能解决你的问题,请参考以下文章