【问题标题】:Why does my quicksort crash on large inputs?为什么我的快速排序在大输入时会崩溃?
【发布时间】:2012-10-28 23:37:34
【问题描述】:

我创建了一个中位数为 3 的标准快速排序实现,它对大量随机整数进行排序。我想增加到至少 1 亿个元素,但最好是 10 亿个。为了提高速度,我尝试在 Cilk++ 中并行化算法。该算法使用双递归,我派生 Cilk 任务来执行每个递归排序。

我的算法适用于最大大小为 10 000 000 的数组。没有 Cilk 关键字,我的顺序算法可以轻松处理 1 亿个元素,但是当我尝试使用 Cilk 时,程序崩溃到桌面。我现在想找出原因。我是否太快地生成了太多 Cilk 任务?

我使用的是 Windows 7 64 位、Intel++ 编译器和集成在 Visual Studio 2010 中的 Intel Parallel Studio XE 2013。该程序被编译为 32 位应用程序。存储随机数据的内存被分配为对 malloc 的单个调用,并检查指针。在中位数计算中,计算中间元素时也要注意整数溢出。

这很可能是缓冲区溢出问题。

这是我的分区:

int pivotvalue = medianOf3(data, low, high);
// pivot is placed next to the last element

int left = low;
int right = high - 1;
while (left < right) {
    while (data[left] < pivotvalue) {
        left++;
        if (left > high) {
            break;
        }
    }
    while (data[right] >= pivotvalue) {
        right--;
        if (right < low) {
            break;
        }
    }

    if (left < right) {
        swap(data, left, right);
    }
}

// restore pivot
swap(data, left, high - 1);
return left;

【问题讨论】:

  • 做中位数时溢出?
  • 我正在使用中间 = ((high - low) / 2) + low;防止溢出还是你的意思是别的?
  • 不管怎样你可能想切换到unsigned ints,或者更好的size_t

标签: c quicksort cilk cilk-plus


【解决方案1】:

对两个子问题递归的 N 个项目的快速排序具有(最坏情况)递归深度 O(N)。通常的解决方法是“tomlogic”建议的解决方法:递归较小的子问题,迭代较大的子问题。这将递归深度减少到 O(lg N)。

修复延续到并行版本。如果串行程序占用堆栈空间 S,Cilk Plus 版本最多占用堆栈空间 PS。 (这个属性不适用于许多其他并行框架。)因此产生较小的子问题并迭代较大的子问题应该使总堆栈空间保持在 O(P lg N) 内。

我是结构化并行编程一本书的作者之一,该书讨论了在 Cilk Plus 和 TBB 中快速排序的并行实现。快速排序的例子(全递归和半递归)可以从http://parallelbook.com/student免费下载。

Arch D. Robison
Intel Corporation

【讨论】:

    【解决方案2】:

    英特尔 Cilk Plus(cilk++ 是不支持的不同产品)对生成深度的限制为 1024。完全有可能溢出双端队列,这会导致崩溃。

    决定在递归树中生成多深是一种平衡行为。您希望有足够的生成以允许工作被盗,但使用太多会增加开销。 cilk_spawn 很便宜,但它不是免费的。如果要排序的元素数量低于某个阈值,最好在快速排序中添加一个检查,并且不要产生递归调用。

    cilk_for 的一个好处是它可以根据你运行的工人数量来优化产卵深度。

    - Barry Tannenbaum
      Intel Cilk Plus Runtime Development
    

    【讨论】:

      【解决方案3】:

      我不知道 Cilk 是如何工作的,但我记得需要在嵌入式平台上修复一个快速排序实现,该实现可能会因递归而溢出堆栈。解决方法是对较小的“一半”数据使用递归调用,并在函数内部处理较大的“一半”(即尾递归)。排序(或反向排序)列表总是会触发溢出,因为调用图的深度等于列表中元素的数量。

      您可以使用调试器找出导致崩溃的原因吗?您能否将数据转储到swap() 中每个条目的日志文件中,然后查看崩溃前函数调用的样子?是否可以在每次调用时转储堆栈的大小? 是否每个 Cilk 任务都有自己的堆栈,可能小于非 Cilk 版本中使用的堆栈?

      您可以通过向swap() 添加带有堆栈地址的参数来跟踪堆栈使用情况。第一次调用和任何新的 Cilk 线程都使用 NULL。如果不是 NULL,则每个递归调用都使用该参数,或者使用其局部变量之一的地址来确定其堆栈的位置。转储地址差异,或者在全局中跟踪它,仅在超过之前的最大值时才报告。

      【讨论】:

      • 很可能是递归过多导致的堆栈溢出。现在的魔法极限似乎在八千万元素左右。我需要修改我的算法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-21
      • 2023-02-01
      • 2014-09-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多