我已经创建了一个中等于3的标准快速排序实现,它对大量随机整数进行排序。我想要达到至少1亿元素,但最好是10亿元素。为了提高速度,我试图在Cilk ++中并行化算法。该算法使用双递归,我生成Cilk任务来执行每个递归排序。
我的算法适用于大小为10 000 000的数组。如果没有Cilk关键字,我的顺序算法很容易处理1亿个元素,但当我尝试使用Cilk时,程序崩溃到桌面。我现在想找出原因。我是否过快地生成了太多的Cilk任务?
我使用的是Windows 7 64位,英特尔++编译器和集成在Visual Studio 2010中的英特尔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;
答案 0 :(得分:3)
我不知道Cilk是如何工作的,但我记得需要在嵌入式平台上修复quicksort实现,这可能会通过递归溢出堆栈。修复是使用递归调用较小的&#34;一半&#34;数据和处理更大的一半&#34;在函数内部(即尾递归)。排序(或反向排序)列表总是会触发溢出,因为调用图的深度等于列表中元素的数量。
您可以使用调试器找出导致崩溃的原因吗?你可以将数据转储到swap()
的每个条目的日志文件中,然后在崩溃之前查看函数调用的内容吗?是否可以在每次调用时转储堆栈的大小? 每个Cilk任务是否都有自己的堆栈,可能比非Cilk版本中使用的堆栈小?
您可以通过向swap()
添加带有堆栈地址的参数来跟踪堆栈使用情况。第一个调用和任何新的Cilk线程使用NULL。每个递归调用使用该参数(如果它不是NULL),或者使用其中一个局部变量的地址来确定其堆栈的位置。转储地址差异,或在全局范围内跟踪它,只有在超过之前的最大值时才报告。
答案 1 :(得分:2)
英特尔Cilk Plus(cilk ++是另一种不受支持的产品)对1024的产生深度有限制。你的双端队列完全可能会溢出,这会导致崩溃。
决定产生递归树的深度是一种平衡行为。您希望有足够的生成来让工作被盗,但是使用太多会增加开销。 cilk_spawn很便宜,但它不是免费的。如果要排序的元素数量低于某个阈值,最好在快速排序中添加一个检查,而不会产生递归调用。
cilk_for的一个好处是它可以根据您运行的工人数量来优化产卵深度。
- Barry Tannenbaum
Intel Cilk Plus Runtime Development
答案 2 :(得分:1)
针对两个子问题递归的N个项目的快速排序具有(最坏情况)递归深度O(N)。通常的解决方案是“tomlogic”建议的:递归较小的子问题,迭代更大的子问题。这会将递归深度减少到O(lg N)。
修复程序继续执行并行版本。如果串行程序占用堆栈空间S,则Cilk Plus版本将占用最多堆栈空间PS。 (许多其他并行框架都没有这个属性。)因此,产生较小的子问题并迭代更大的子问题应该将总堆栈空间保持在O(P lg N)之内。
我是结构化并行编程一书的作者之一,该书讨论了Cilk Plus和TBB中Quicksort的并行实现。 Quicksort的示例(完全递归和半递归)可以从http://parallelbook.com/student免费下载。
Arch D. Robison
Intel Corporation