20

这是这个优秀问题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作用于现有的.NET 集合,与作用于相同集合的(在我们的例子中)List<T>相比,OrderBy它在处理中的开销(如果有的话)应该更小,但是必须返回另一个集合。但仍然表现得要好得多。与类型相比可能会有一定的开销,但无论如何都会对现有列表起作用!此外,我很高兴看到一种方法比现有的 .NET 方法运行得更快。List<T>personsIOrderedEnumerable<T>OrderByList<T>IEnumerable<T>SortLinq

原始问题中的所有答案都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 毫秒。所以确实我的结论是错误的。一旦我开始列举这些收藏品,它们的表现就相当了。我更喜欢anyday的语法OrderBy:)

编辑 2:我刚刚发现这与完全相同。但是这里有一个更有趣的问题,一般来说是关于延迟执行,虽然不是完全关于排序。

4

3 回答 3

37

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

在您枚举结果之前,查询是deferred,因此它实际上从未进行排序。在您实际枚举结果之前,IOrderedEnumerable<T>不会处理输入并进行任何形式的排序。

尝试将基准更改为:

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

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

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

于 2012-11-01T16:34:26.630 回答
12

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

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

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

于 2012-11-01T16:34:08.437 回答
2

OrderBy()不创建排序列表。

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

于 2012-11-01T19:39:36.407 回答