2009-12-02 145 views
79

我可以使用Sort或OrderBy对列表进行排序。哪一个更快?两个工作都是相同的 算法?C#排序和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); 
    } 
} 
+0

我不能相信,没有一个答案中提到这一点,但最大的区别在于:OrderBy对Array或List进行了排序,而Sort实际上对它进行排序。 – PRMan 2018-02-16 23:39:47

回答

81

为什么不衡量它:

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: {0}ms", watch.ElapsedMilliseconds); 

     watch = Stopwatch.StartNew(); 
     for (int i = 0; i < COUNT; i++) 
     { 
      OrderBy(persons); 
     } 
     watch.Stop(); 
     Console.WriteLine("OrderBy: {0}ms", 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(); 
    } 
} 

在我的电脑在Release模式下编译该程序打印时:

Sort: 1162ms 
OrderBy: 1269ms 

UPDATE:

正如所建议的@斯蒂芬在这里是少数次排列大列表的结果:

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: {0}ms", watch.ElapsedMilliseconds); 

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

打印:

Sort: 8965ms 
OrderBy: 8460ms 

在这种情况下,它看起来像排序依据执行得更好。


UPDATE2:

而且使用随机的名字:

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 

仍然排序依据是更快

+2

我认为,将一个非常小的列表(3个项目)排序1000000次,或者通过几次排序非常大的列表(1000000个项目),会有很大的不同。两者都非常相关。在实践中,中等大小的列表(中等?...比如说现在1000个项目)是最有趣的。恕我直言,排序列表与3项不是很有意义。 – 2009-12-02 12:58:53

+0

@Stefan,实际上这个名单可能确实更多。重点是它演示了速度差异。 – James 2009-12-02 13:06:38

+17

请注意,“更快”和“明显更快”之间存在差异。在你的最后一个例子中,差异约为四分之一秒。用户是否注意到?用户等待近9秒钟的结果是不可接受的?如果两个问题的答案都是“否”,那么从绩效角度选择哪一个确实无关紧要。 – 2009-12-02 16:10:20

91

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

它也取决于你是否缓冲查询与迭代它几次(LINQ到对象,除非你缓冲结果,将按照foreach重新排序)。

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

OrderBy(n => n.Name, StringComparer.{yourchoice}IgnoreCase); 

(为{yourchoice}CurrentCultureOrdinalInvariantCulture一个)。

List<T>.Sort

该方法使用的Array.Sort,其 使用快速排序算法。这个 执行执行不稳定的 排序;也就是说,如果两个元素是 相等,则它们的顺序可能不会被保存为 。相反,稳定的分类 保留了 相等的元素的顺序。

Enumerable.OrderBy

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

+7

+1“OrderBy被记录为稳定” – Lev 2012-09-18 12:17:23

+5

如果您使用.NET Reflector或ILSpy破解打开的'Enumerable.OrderBy'并深入到其内部实现中,可以看到OrderBy排序算法是QuickSort的一种变体,做一个稳定的排序。 (参见'System.Linq.EnumerableSorter '。)因此,'Array.Sort'和'Enumerable.OrderBy'都可以预期具有_O(N log N)_个执行时间,其中_N_是采集。 – 2013-06-26 16:25:19

+0

@Marc如果两个元素是平等的,并且他们的顺序没有保留,我不太会遵循区别。对于原始数据类型,这看起来不是什么问题。但即使是一个参考类型,如果我要分类,为什么会这么重要,名字为Marc Gravell的人出现在另一个名字为Marc Gravell(例如:)的人之前?我不是在质疑你的答案/知识,而是在寻找这种情况的应用。 – Mukus 2014-03-17 02:58:33

31

我觉得要注意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方法ñ倍,而Sort选项可能会调用CalculateSalary高达2n log(n)倍,这取决于排序算法的成功。

+3

这是真的,尽管存在解决该问题的方法,即将数据保存在数组中,并使用Array.Sort重载,该重载使用两个数组,一个键和另一个值。在填写关键数组时,您会调用CalculateSalary'n'次。这显然不如使用OrderBy那么方便。 – phoog 2015-06-09 02:58:40

0

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

我已经搜索了orderby's,但我甚至在msdn库中都找不到任何信息。 如果你没有任何相同的值和排序相关的只有一个属性,我更喜欢使用 Sort()方法;如果不是,则使用OrderBy。

+0

@phoog感谢您的更新! – icaptan 2015-06-24 13:15:16

+1

根据当前的MSDN文档排序使用3种基于输入的不同排序算法。其中有QuickSort。 OrderBy()算法的问题在这里(Quicksort):https://stackoverflow.com/questions/2792074/what-sorting-algorithm-is-used-by-linq-orderby – Thor 2017-06-21 15:50:43

50

Darin Dimitrov的回答显示OrderBy在面对已排序的输入时比List.Sort稍快。我修改了他的代码,以便重复排序未排序的数据,并且在大多数情况下,它会稍微慢一些。

此外,OrderBy测试使用ToArray迫使LINQ的枚举的枚举,而是明显地返回一个类型(Person[]),其是从所述输入类型(List<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: {0}ms", 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: {0}ms", 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: {0}ms", 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(); 
    } 
} 
+1

我现在在LinqPad 5中运行测试代码( .net 5),'OrderByWithToList'和'OrderBy'的取值相同。 – dovid 2017-03-06 19:47:44