并行化 std::nth_element 和 std::partition

Posted

技术标签:

【中文标题】并行化 std::nth_element 和 std::partition【英文标题】:Parallelizing std::nth_element and std::partition 【发布时间】:2013-08-29 16:00:38 【问题描述】:

我正在将使用 std::nth_elementstd::partition 的 C++ 代码移植到 OpenCL。

nth_element 是一个selection algorithm,它将第 n 个最小的数字放在数组中的第 n 个位置,并排列剩余的元素,使小于该数字的所有元素在数组中位于它之前,所有大于该数字的元素比它之后。实际上,nth_element 将数组分为 3 个桶:数字本身、小于它的所有数字和大于它的所有数字。

典型地,nth_element 是使用递归分区实现的:选择一个元素,根据元素是否小于该元素进行分区。然后,选择包含数组的第 n 个元素的存储桶并在该存储桶上递归。 nth_element 和完整快速排序之间的主要区别在于快速排序在两个存储桶上递归,而不仅仅是包含第 n 个元素的存储桶。


partitionnth_element 的弱版本,它只将数组排序到 2 个桶中:条件为真的桶和条件为假的桶。我链接到的网站给出了实现:

while (first!=last) 
    while (pred(*first)) 
        ++first;
        if (first==last) return first;
    
    do 
        --last;
        if (first==last) return first;
     while (!pred(*last));
    swap (*first,*last);
    ++first;

return first;

其中 pred 是一个函数,用于评估一个元素是否应该在第一个桶中。基本上,这个函数迭代地找到数组的最外层元素对,它们在错误的位置,并交换它们,当这对元素是相同的元素时停止。


这是我对并行化 nth_elementpartition 的初步想法:

可以使用原子比较和交换来实现分区,但我不确定如何覆盖所有可能的可以交换的值对。没有明显的方法可以在多个线程之间划分工作,因为分区需要比较可能彼此相邻或位于数组两端的元素。我也没有看到避免让线程 B 与已经被线程 A 交换的元素进行比较的方法,这是低效的。

nth_element 似乎更难以并行化,因为它是递归的:每个分区都依赖于由前一个分区部分排序的元素。

据推测,有效的并行化策略将需要与典型串行代码完全不同的方法,用于这两种功能。


nth_elementpartition 的高效并行实现是否已经存在?如果不是,什么是好的并行化策略?

【问题讨论】:

nth_element 比递归更迭代(不像快速排序,它需要两次递归调用);矛盾的是,一旦完成初始(和最大)分区,这使得快速排序更容易并行。谷歌搜索“并行分区”发现了这个演示文稿:lsi.upc.edu/~lfrias/research/parpar/wea08.pdf 以及其他有趣的点击。 @rici 关于迭代与递归的好点。但是,由于nth_element 中的迭代完全依赖于先前迭代的结果,因此也可以将其视为递归。另外,感谢您的链接!里面有一些非常好的想法。 分区可以通过使用 std::advance() 将范围分成大致相等的两个部分来实现。然后,您使用 2 个线程对每个使用单线程版本进行分区。之后,您需要在两个中间部分上调用旋转。这就像 stable_partition 一分为二。 【参考方案1】:

Cuda THRUST 已实现分区功能 (http://docs.nvidia.com/cuda/thrust/index.html#reordering)。

主要思想应该如下: 使用前缀和计算元素在数组中的位置,然后重新排列数组。

【讨论】:

以上是关于并行化 std::nth_element 和 std::partition的主要内容,如果未能解决你的问题,请参考以下文章

为啥 std::nth_element 返回 N < 33 个元素的输入向量的排序向量?

我应该在 openMP 并行区域内使用 gnu 并行模式函数吗(for-loop,tasks)

了解 nth_element

为啥使用指针的 STL 算法比 std::vector 迭代器快得多?

openMP 没有并行线程

如何在 Matlab 中使用并行循环