【问题标题】:Why is OrderBy which returns IOrderedEnumerable<T> much faster than Sort?为什么返回 IOrderedEnumerable<T> 的 OrderBy 比 Sort 快得多?
【发布时间】:2012-10-22 07:14:08
【问题描述】:

这是对这个优秀问题C# Sort and OrderBy comparison 的跟进。我将使用相同的示例:

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

争用的方法有:

persons.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
//and
persons.OrderBy(n => n.Name);

首先让我说,我知道没有任何显着的性能差异需要担心。但我很想知道为什么OrderBy 的性能比Sort 好得多。我正在使用@phoog 在原始问题中发布的答案。

private void button1_Click(object sender, EventArgs e)
{
    IEnumerable<Person> people;

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

    BenchMark(persons => people = persons.OrderBy(n => n.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 static void BenchMark(Action<List<Person>> action)
{
    List<Person> persons = new List<Person>();
    for (int i = 0; i < 10000; i++)
    {
        persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
    }
    List<Person> unsortedPersons = new List<Person>(persons);

    Stopwatch watch = new Stopwatch();
    for (int i = 0; i < 100; i++)
    {
        watch.Start();

        action(persons);

        watch.Stop();
        persons.Clear();
        persons.AddRange(unsortedPersons);
    }

    MessageBox.Show(watch.Elapsed.TotalMilliseconds.ToString());
}

结果:

Sort() => 3500 ~ 5000 ms
OrderBy() => 0.2 ~ 1.5 ms

尽管我最初测试的列表越小,差异也很大,但随着集合规模的增加,这种差异变得越来越明显。可能是我遗漏了一些理解 .NET 集合的关键,但我的想法是因为Sort 作用于现有的List&lt;T&gt;,与OrderBy 相比,它在处理中的开销(如果有的话)应该更少。在同一个List&lt;T&gt;(在我们的例子中为persons)但必须返回另一个集合IOrderedEnumerable&lt;T&gt;。但OrderBy 的表现仍然要好得多。与IEnumerable&lt;T&gt; 类型相比,List&lt;T&gt; 可能有一定的开销,但Sort 无论如何都会作用于现有列表!此外,看到Linq 方法比现有的.NET 方法运行得更快,我感到很高兴。

原始问题中的所有答案都将SortOrderBy.ToList 进行比较,我认为这会产生一些开销,因此或多或少地表现相同。

可能有哪些实现差异?


编辑:好的,我学到了一些新东西。以下是我确认延迟执行的方式。

private void button1_Click(object sender, EventArgs e)
{
    BenchMark(persons =>
    {
        persons.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
        foreach (var item in persons)
        {
            break;
        }
    });

    BenchMark(persons =>
    {
        IEnumerable<Person> people = persons.OrderBy(n => n.Name);
        foreach (var item in people)
        {
            break;
        }
    });
}

Sort 运行时间为 4000 - 5000 毫秒,而 OrderBy 运行时间略高于 5000 毫秒。所以确实我的结论是错误的。一旦我开始列举这些收藏品,它们的表现就相当了。我更喜欢OrderBy anyday 的语法:)

编辑 2: 我刚刚发现这与 this one 完全相同。但这里有一个more interesting question about deferred execution in general,虽然不是完全订购。

【问题讨论】:

标签: c# .net linq sorting collections


【解决方案1】:

在这种情况下,OrderBy 要快得多,因为您实际上并没有执行它。

在您枚举结果之前,查询是延迟的,因此它实际上从未进行排序。在您真正枚举结果之前,IOrderedEnumerable&lt;T&gt; 不会处理输入并执行任何形式的排序。

尝试将基准更改为:

 BenchMark(persons => people = persons.OrderBy(n => n.Name).Count());

Count() 调用将强制排序实际发生(因为它需要枚举 IOrderedEnumerable&lt;T&gt; 以生成计数),这应该会显着平衡您的时间。

大多数 LINQ 扩展方法都以这种方式工作 - 直到您枚举它们(通过 Count()、调用 ToList() 或仅在正常的 foreach 循环中使用它们等),它们的影响可以忽略不计,因为它们不会除了构建可枚举之外,实际上并没有直接做任何事情。与OrderBy(...).ToList() 相比,其他基准测试的原因是添加ToList() 会强制OrderBy 完全执行并实际排序结果。

【讨论】:

  • 只要微软没有偷偷摸摸并意识到他们不需要对集合进行排序来返回它的计数......:p
  • @Rawling 至少目前,这种优化还没有到位。 OrderBy 总是返回一个IOrderedEnumerable&lt;T&gt;,并且该类没有实现ICollection&lt;T&gt; 的版本,因此不会发生对ICollection&lt;T&gt; 的正常Count() 优化。
  • @nawfal 这些行是同步执行的,你只是误解了IOrderedEnumerable 给你的东西。它不会给你一个已经订购的东西,它给你一个承诺一旦你开始枚举就会订购的东西。这称为延迟执行。您可以通过 google 了解更多信息。
  • @nawfal 这是 LINQ 的一大优点,尽管它有时会令人沮丧。但是,当您开始查看 IQueryable&lt;T&gt; 时,这很有意义 - 如果没有这个“概念”,您将不得不更频繁地访问数据库。即使使用 LINQ to Objects,它也可能非常有益 - 例如,它是 PLINQ 以相同方式“正常工作”的重要组成部分。
  • @Random832 好吧 - OrderBy 需要创建一些实现 ICollection 的 IOrderedEnumerable&lt;T&gt; 实现,因此计数可用。但是,是的,这本来可以做到的。但是,当前的实现并没有这样做。
【解决方案2】:

OrderBy() 与大多数 LINQ 方法一样,使用延迟执行。

在您枚举其结果之前,它实际上并没有做任何事情。

要正确衡量其性能,您可以致电.OrderBy(...).Count()

【讨论】:

  • SLaks,你能解释一下第二句话吗?我不是很清楚
  • BenchMark(persons => people = persons.OrderBy(n => n.Name)).Count();
【解决方案3】:

OrderBy() 不创建排序列表。

它创建一个 IEnumerable 对象,当您枚举它时,它会生成一个排序列表。在您枚举列表之前,不会发生实际的排序。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-01
    • 1970-01-01
    • 2014-04-21
    • 2015-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多