【问题标题】:Performance of built-in .NET collection sorters内置 .NET 集合分类器的性能
【发布时间】:2011-04-13 22:05:24
【问题描述】:

有一个关于如何对列表进行排序的问题。从基本的 List.Sort() 到 List.OrderBy() 有几种方法。最可笑的是roll-your-own-SelectionSort。我立即投了反对票,但这让我思考;应用于列表的 Linq 的 OrderBy() 不会做同样的事情吗? myList.OrderBy(x=>x.Property).ToList() 将产生一个迭代器,它基本上在集合的左侧找到投影的最小值,并返回它。遍历整个列表时,这就是选择排序。

这让我思考; Lists、SortedLists、Enumerables 等的内置排序器使用什么算法,并且通过扩展,对于大型集合是否应该避免使用它们中的任何一种? SortedList,因为它保持按键排序,可能会在每次添加时使用单遍 InsertionSort;找到值大于新索引的第一个索引,并在它之前插入。列表和数组本身可能非常有效地合并排序,但我不知道 Sort() 背后的实际算法。我们已经讨论了 OrderBy。

我在上面所知道的似乎表明 List.Sort() 或 Array.Sort() 是已知大小列表的最佳选择,不鼓励使用 Linq 对内存中的列表或数组进行排序。对于流,除了 OrderBy() 枚举之外真的没有其他方法了;您可以将数据保留为流,而不必在排序之前将其全部保存,从而减轻了性能损失。

编辑:

普遍的共识是,给定一个列表或数组的具体实现,Sort() 会更快。 OrderBy 是合理的,但速度较慢,因为它增加了从传递的枚举中提取数组的 O(N) 复杂性。 SortedList 初始化最终是 O(N^2) 因为引擎盖下的东西。故事的寓意,当你有一个实际的 List 时,使用 List.Sort() 而不是 List.OrderBy()。

【问题讨论】:

  • 我认为大多数内置排序都使用快速排序。如果要加快速度,请删除边界检查。 List.Sort 在内部也使用 Array.Sort。
  • @Mikael 是正确的,OrderBy() 也使用快速排序。 @KeithS,您可以愉快地自己浏览源代码,它是公开的(并集成到 VS 中)。 EnumerableSorter.QuickSort 是 OrderBy 使用的方法的名称。
  • .Net Reflector 再次救援 - 一定会爱上它!
  • @Mikael:你不能在 .NET 中进行边界检查
  • @Henk:我的意思是,避免对集合长度进行边界检查。所有 .Sort() 方法都在开始时进行检查。对于时间紧迫的系统,您可以通过自己实现并跳过长度/索引检查来节省时间。

标签: .net performance sorting collections


【解决方案1】:

Enumerable.OrderBy() 将 IEnumerable 放入一个数组并使用快速排序。 O(n) 存储要求。它由 System.Core.dll 中的内部类EnumerableSort<TElement>.QuickSort() 完成。存储成本使得简单地对列表进行排序(如果有的话)没有竞争力,因为 List 就地排序。 Linq 通常通过使用 is 运算符检查 IEnumerable 的真实功能来进行优化。在这里不起作用,因为 List.Sort 是破坏性的。

List.Sort 和 Array.Sort 使用就地快速排序。

SortedList 的插入复杂度为 O(n),超过了查找插入点的 O(log(n)) 复杂度。因此,将 N 个未排序的项目放入其中将花费 O(n^2)。 SortedDictionary 使用红黑树,插入复杂度为 O(log(n))。因此 O(nlog(n)) 来填充它,与摊销快速排序相同。

【讨论】:

  • 为什么 SortedList 有 O(n) 用于插入?我认为 BinarySearch 使它成为 O(log(N) )
  • @Andreas - 它必须为要插入的元素腾出空间。这需要移动 O(n) 个元素。它是引擎盖下的一个数组。
  • 嗯。现在我想知道,如果 SortedList 使用带有“中心”引用的双向链表实现会怎样?接近 O(N) 来索引单个元素(您可以从一端或中心开始并朝着实际的“索引”工作),但也 O(N) 来迭代(“下一个”很便宜)和插入,给定O(logN) 二进制搜索(您可以从中心开始),对于 O(logN) 的总插入复杂度,将是恒定的(重新分配两个指针)。这将使排序的双向链表 O(NlogN) 复杂度来填充 N 个未排序的元素。
  • @Keith:大哦不考虑将算法一分为二。您将从链表中获得的较小的 Oh 完全被现代机器上 CPU 缓存的工作方式所击败。它经过高度优化,可以从 RAM 加载连续字节的内存。链表的缓存局部性很差,在缓存未命中时会使 CPU 停顿数百个周期。这就是为什么 List 实际上是一个数组,而不是传统数据结构教科书中的链表。
  • KeithS:如果您想对SortedList 进行 O(lg n) 操作,您只需使用SortedDictionary,因为SortedList 实际上是KeyValuePair 元素的列表。
【解决方案2】:

快速浏览反射器告诉我,列表排序方法通过 System.Collections.Generic.GenericArraySortHelper 使用快速排序 http://en.wikipedia.org/wiki/Quicksort

SortedList 使用 Array.BinarySearch 来确定在每个 Add 上插入内容的位置

枚举器没有排序逻辑

在大多数情况下,快速排序是一个不错的排序选择,尽管如果您对输入数据真的不走运,它可能会接近 O(n^2)。

如果您怀疑您的输入数据是 巨大 堆以不走运(已排序)的顺序进行快速排序的数据,则一个技巧是先随机化数据(这总是很便宜),然后执行随机数据的排序。快速排序算法可以实现一些技巧来缓解对已经排序(或接近排序)的输入数据进行排序的问题,我不知道 BCL 实现是否可以做到这些。

【讨论】:

    【解决方案3】:

    是的,你的假设听起来是对的。我做了一个小测试来确认它。

    在 5000000 个整数上,

    data.Sort();                           //  500 ms
    data = data.OrderBy(a => a).ToList();  // 5000 ms
    

    【讨论】:

    • 这可能表明 OrderBy 不适用于大型集合,但可能不是因为我所说的原因。显然使用 OrderBy 需要了解整个 enumerable,这会破坏无序 Linq 迭代器的流传输质量。
    【解决方案4】:

    找出每种方法的性能的一种方法是对其进行测量:

    List<int> createUnsortedList()
    {
        List<int> list = new List<int>();
        for (int i = 0; i < 1000000; ++i)
            list.Add(random.Next());
        return list;
    }
    
    void Method1()
    {
        List<int> list = createUnsortedList();
        list.Sort();
    }
    
    void Method2()
    {
        List<int> list = createUnsortedList();
        list.OrderBy(x => x).ToList();
    }
    

    结果:

    • 方法 1:0.67 秒(List.Sort)
    • 方法 2:3.10 秒(OrderBy)

    这表明即使对于非常大的列表,OrderBy 的性能也是合理的,但不如在列表上使用内置的 Sort 方法那么快。这可能是因为 OrderBy 的代码稍微灵活一些——它需要一个必须为每个元素评估的键选择器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-02
      • 1970-01-01
      • 2011-04-09
      • 1970-01-01
      • 2011-05-15
      • 2010-09-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多