【问题标题】:Merge Sort -- Optimization by Cache Line Size?合并排序——通过缓存行大小进行优化?
【发布时间】:2015-10-16 17:44:39
【问题描述】:

我的一位朋友最近提到,您可以通过“缩短合并排序”来减少合并排序的实际运行时间。他提到,您应该在各个数组大小等于缓存行大小的地方停下来,而不是将数组一直分解为单个块,因为整个数组随后将被加载到缓存中。此时,您应该使用替代排序(即插入排序)来合并每个数组,然后完成合并排序。

虽然 BigO 提出了不同的建议,但他的建议似乎具有直观意义。有人可以确认或否认这一点,和/或提供有关其如何以及为何起作用的更多信息吗?

感谢大家的帮助!

【问题讨论】:

    标签: arrays sorting caching mergesort insertion-sort


    【解决方案1】:

    插入排序的组合创建小运行,然后切换到合并排序称为 timsort。维基文章:

    http://en.wikipedia.org/wiki/Timsort

    【讨论】:

      【解决方案2】:

      嗯,(有点抽象的)答案是 Big-O 仅对大数有用:它丢弃常数因子:O(n)=O(3n),它丢弃低阶项:O(n² +3n)=O(n²)。所以是的,你无法从 Big-O 符号中分辨出来。

      此外,Big-O 表示法通常用于一个非常简单的模型,其中每个“操作”只花费 1,并且它不知道缓存。

      这就是模型没有告诉您这可能有用的原因。我想你可以看看 Donald E. Knuth 的“排序和搜索”,他在其中对虚构的汇编语言进行运行时分析,直到更低的术语(但仍然没有考虑缓存,IIRC)。

      【讨论】:

        【解决方案3】:

        使用 OΩΘ 等)分析复杂度仅用于描述算法作为输入的执行情况尺寸增长。如果您查看实际函数,您会发现随着输入的增长,常数因子变得不那么重要。总体而言,输入大小在函数中占主导地位。

        在实践中,常量因素确实很重要(缓存未命中、指令延迟等),这就是为什么RadixSort 通常很少使用的原因。例如,从寄存器读取大约需要从缓存中最低级别读取的时间的 1/5(大约是下一级时间的 1/5,等等)。由于它们是数量级的,因此在实践中缓存成本通常支配着算法的实际性能。

        插入排序确实相当有效地使用缓存,通常只要数据适合缓存。由于它是连续的,因此它也与预测器很好地交互。两者通常都是较小输入更好的原因。另一个很好的例子是QuickSort,它在技术上是O(n^2),但在实践中仍然大量使用,因为它具有更好的缓存特性。 TimSort(Python 和 Java 的默认设置)也对较小的输入使用插入排序。

        【讨论】:

        • 在我的系统上,Intel 2600K 3.4 ghz,对 4,194,304 个 64 位无符号整数的伪随机数据进行排序的时间:基数排序 - 203 ms ;合并排序 - 297 毫秒;微软 std::sort - 344 毫秒;微软 std::stable_sort - 375 毫秒。 std::sort 是intro sort。 std::stable_sort 是一种自下而上的合并排序,它使用一个大小为原始数组 1/2 的临时数组,执行一些额外的副本和最后的合并步骤。
        • 使用位图进行计数排序能够在 250 毫秒内在我的笔记本电脑 (x86_64 Intel(R) Core(TM) i7-3632QM CPU @ 2.20GHz) 上进行排序,即使编译器生成损坏的代码 ( std::sort 为 314 毫秒)。它比 intro-sort 具有更高的内存开销,但它像 radix-sort(1/4 分支未命中)一样是伪无分支的,因此它仍然平均具有更高的指令吞吐量(2/cycle vs. 1.9/cycle)。我不确定为什么合并排序会胜过它。我认为扩展排序在某些情况下也被证明优于引入排序。
        猜你喜欢
        • 1970-01-01
        • 2012-09-17
        • 2018-07-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-18
        • 2011-11-09
        • 1970-01-01
        相关资源
        最近更新 更多