C#Sort和OrderBy比较


105

我可以使用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);
    }
}

22
我不敢相信所有答案都没有提到这一点,但是最大的不同是:OrderBy对Array或List进行排序,而Sort实际上对它进行排序。
PRMan

2
如标题所说的比较,我想补充一点,OrderBy是稳定的,并且排序最多可稳定16个元素,因为如果元素多于16个元素,则使用插入的排序方式,如果元素多于16个元素,则切换到其他不稳定的算法。编辑:稳定意味着保持相对顺序具有相同键的元素。
Eklavyaa

@PRMan不,OrderBy创建了一个可枚举的惰性对象。仅当您在返回的枚举数上调用ToList之类的方法时,您才能获得排序的副本。
斯图尔特

1
@Stewart,您不认为将Array.Copy或Collection.Copy放入System.Core / System / Linq / Enumerable.cs中的Buffer中的TElement []中是副本吗?而且,如果您在IEnumerable上调用ToList,则您暂时可以一次在内存中保存3个副本。对于非常大的阵列,这是一个问题,这是我的观点之一。另外,如果您不止一次需要相同的排序顺序,则由于其持久性,一次调用就地排序比重复对列表进行排序要有效得多。
PRMan

1
@PRMan哦,您的意思是内部整理了一个副本。但这仍然是不准确的,因为OrderBy不会创建副本-从我的看到,这实际上是在您开始遍历集合时由GetEnumerator方法完成的。我只是尝试单步执行代码,发现从LINQ表达式填充变量的代码几乎立即运行,但是当您进入foreach循环时,它将花费时间对其进行排序。我想当我有更多时间时,我应该花点时间来弄清楚它在幕后的工作方式。
斯图尔特

Answers:


90

为什么不测量它:

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();
    }
}

在我的计算机上,以“发布”模式编译时,该程序将打印:

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: {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

在这种情况下,看起来OrderBy表现更好。


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

仍然OrderBy更快


2
我认为,对一个非常小的列表(3个项目)进行1000000次排序,或者对一个非常大的列表(1000000个项目)进行几次排序是完全不同的。两者都很相关。实际上,列表的中等大小(什么是中等?……现在说1000个项目)是最有趣的。恕我直言,对3个项目进行排序并不是很有意义。
Stefan Steinegger

25
注意,“更快”和“明显更快”之间是有区别的。在您的最后一个示例中,差异约为四分之一秒。用户会注意到吗?用户等待将近九秒钟才能得到结果,这是不可接受的吗?如果对两个问题的回答均为“否”,那么从性能角度选择哪一个问题都没有关系。
Eric Lippert

12
还要注意,这里的测试是在启动秒表之前对列表进行排序的,因此我们正在比较两种算法在面对排序后的输入时的比较方式。这可能与它们在未分类的输入下的相对性能完全不同。
phoog 2012年

3
考虑LINQ到与就地List<T>.Sort实现相比必须花费额外的内存这一事实,恕我直言,这些结果令人惊讶。我不确定他们是否在较新的.NET版本中对此进行了改进,但是在我的机器(i7第3代64位.NET 4.5版本)上,它们在所有情况下的Sort性能OrderBy都超过了。此外,通过查看OrderedEnumerable<T>源代码,它似乎会创建三个附加数组(首先是a Buffer<T>,然后是投影键的数组,然后是索引的数组),然后最终调用Quicksort对索引数组进行排序。
Groo

2
...然后完成所有这些操作,ToArray将创建结果数组。内存操作和数组索引是非常快的操作,但是我仍然找不到这些结果背后的逻辑。
Groo

121

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

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

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

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

(对{yourchoice}之一CurrentCultureOrdinalInvariantCulture)。

List<T>.Sort

此方法使用Array.Sort,后者使用QuickSort算法。此实现执行不稳定的排序;也就是说,如果两个元素相等,则可能不会保留其顺序。相反,稳定排序保留了元素相等的顺序。

Enumerable.OrderBy

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


5
如果使用.NET Reflector或ILSpy破解Enumerable.OrderBy并深入研究其内部实现,则可以看到OrderBy排序算法是QuickSort的变体,可以稳定地进行排序。(请参阅参考资料System.Linq.EnumerableSorter<TElement>。)因此,Array.Sort并且Enumerable.OrderBy可以预期两者都具有O(N log N)个执行时间,其中N是集合中元素的数量。
2013年

@Marc我不太明白如果两个元素相等并且它们的顺序没有保留,会有什么不同。对于原始数据类型,这当然看起来不成问题。但是,即使对于引用类型,如果我要进行排序,为什么名叫Marc Gravell的人也出现在另一个名叫Marc Gravell的人(例如:)之前呢?我不是在问您的答案/知识,而是在寻找这种情况的应用程序。
Mukus 2014年

4
@Mukus想象您按名称(或实际上按出生日期)对公司地址簿进行排序-不可避免地会有重复。最终的问题是:对他们会发生什么?是否定义了子订单?
Marc Gravell

55

达林·迪米特洛夫(Darin Dimitrov)的答案表明,这OrderByList.Sort面对已经分类的输入要快一些。我修改了他的代码,以便它反复对未排序的数据进行排序,并且OrderBy在大多数情况下会稍慢一些。

此外,该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();
    }
}

2
我现在在LinqPad 5(.net 5)中运行测试代码,OrderByWithToList所需时间与相同OrderBy
dovid

38

我认为重要的是要注意Sort和之间的另一个区别OrderBy

假设存在一个 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最多调用2 n log(n次,具体取决于排序算法的成功程度。


4
的确如此,尽管对此问题有解决方案,即将数据保存在数组中并使用Array.Sort重载,它需要两个数组,一个是键,另一个是值。在填充键数组时,您将调用CalculateSalary n时间。显然,这不如使用OrderBy方便。
phoog

14

简而言之 :

列表/数组Sort():

  • 不稳定的排序。
  • 就地完成。
  • 使用Introsort / Quicksort。
  • 通过提供比较器来完成自定义比较。如果比较昂贵,则它可能比OrderBy()慢(后者允许使用键,请参见下文)。

OrderBy / ThenBy():

  • 排序稳定。
  • 不到位。
  • 使用Quicksort。Quicksort不是稳定的排序。这是窍门:排序时,如果两个元素具有相同的键,它将比较它们的初始顺序(在排序之前已存储)。
  • 允许使用键(使用lambda)对元素值进行排序(例如:)x => x.Id。在排序之前,首先提取所有密钥。与使用Sort()和自定义比较器相比,这可能会导致更好的性能。

来源: MDSN参考源dotnet / coreclr存储库(GitHub)。

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


0

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

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


1
根据当前的MSDN文档,排序基于输入使用3种不同的排序算法。其中包括QuickSort。问题的排序依据()算法是在这里(快速排序):stackoverflow.com/questions/2792074/...
雷神

-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)。


3
问题:您回答中的O(n)时间(我假设)是指OrderBy还是Comparer?我认为快速排序无法达到O(N)时间。
科夫曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.