gpt4 book ai didi

c++ - C OpenMP并行quickSort

转载 作者:可可西里 更新时间:2023-11-01 16:29:58 26 4
gpt4 key购买 nike

在C++中使用openMP时,我再次陷入困境。这次,我正在尝试实现并行quicksort。

代码:

#include <iostream>
#include <vector>
#include <stack>
#include <utility>
#include <omp.h>
#include <stdio.h>

#define SWITCH_LIMIT 1000

using namespace std;

template <typename T>
void insertionSort(std::vector<T> &v, int q, int r)
{
int key, i;
for(int j = q + 1; j <= r; ++j)
{
key = v[j];
i = j - 1;
while( i >= q && v[i] > key )
{
v[i+1] = v[i];
--i;
}
v[i+1] = key;
}
}

stack<pair<int,int> > s;

template <typename T>
void qs(vector<T> &v, int q, int r)
{
T pivot;
int i = q - 1, j = r;
//switch to insertion sort for small data
if(r - q < SWITCH_LIMIT)
{
insertionSort(v, q, r);
return;
}

pivot = v[r];
while(true)
{
while(v[++i] < pivot);
while(v[--j] > pivot);
if(i >= j) break;
std::swap(v[i], v[j]);
}
std::swap(v[i], v[r]);

#pragma omp critical
{
s.push(make_pair(q, i - 1));
s.push(make_pair(i + 1, r));
}
}

int main()
{
int n, x;
int numThreads = 4, numBusyThreads = 0;
bool *idle = new bool[numThreads];
for(int i = 0; i < numThreads; ++i)
idle[i] = true;
pair<int, int> p;
vector<int> v;
cin >> n;
for(int i = 0; i < n; ++i)
{
cin >> x;
v.push_back(x);
}
cout << v.size() << endl;
s.push(make_pair(0, v.size()));

#pragma omp parallel shared(s, v, idle, numThreads, numBusyThreads, p)
{
bool done = false;
while(!done)
{
int id = omp_get_thread_num();
#pragma omp critical
{
if(s.empty() == false && numBusyThreads < numThreads)
{
++numBusyThreads;
//the current thread is not idle anymore
//it will get the interval [q, r] from stack
//and run qs on it
idle[id] = false;
p = s.top();
s.pop();
}
if(numBusyThreads == 0)
{
done = true;
}
}
if(idle[id] == false)
{

qs(v, p.first, p.second);
idle[id] = true;
#pragma omp critical
--numBusyThreads;
}

}
}
return 0;
}

算法:

要将openMP用于递归函数,我使用了一个堆栈来跟踪qs函数应在其上运行的下一个间隔。我手动添加了第一个间隔[0,size],然后在堆栈中添加了新间隔时让线程开始工作。

问题:

该程序为时过早,如果您看一下代码,则不会在创建第一组间隔([q,i-1],[i + 1,r])后对数组进行排序。我的猜测是得到工作的线程会考虑默认情况下共享的quicksort函数的局部变量(代码中的qs),因此它们会将它们弄乱,并且不会在堆栈中添加任何间隔。

我如何编译:
g++ -o qs qs.cc -Wall -fopenmp

我如何运行:
./qs < in_100000 > out_100000
其中in_100000是第一行包含100000的文件,其后一行由空格分隔的100k整数。

我在Linux上使用gcc 4.5.2

感谢您的帮助,

最佳答案

我实际上没有运行您的代码,但是我看到p上立即出现错误,应该是private而不是shared。并行调用qs:qs(v, p.first, p.second);将在p上引起种族,从而导致不可预测的行为。 qs的局部变量应该可以,因为所有线程都有自己的堆栈。但是,总体方法是好的。您走在正确的轨道上。

这是我对并行quicksort的实现的一般性评论。 Quicksort本身令人尴尬地是并行的,这意味着不需要同步。分区数组上的qs的递归调用令人尴尬地是并行的。

但是,并行性以递归形式公开。如果仅在OpenMP中使用嵌套并行机制,那么最终将在一秒钟内拥有数千个线程。无法获得加速。因此,大多数情况下,您需要将递归算法转换为交互式算法。然后,您需要实现一种工作队列。这是您的方法。而且,这并不容易。

对于您的方法,有一个很好的基准:OmpSCR。您可以在http://sourceforge.net/projects/ompscr/下载

在基准测试中,有几种基于OpenMP的快速排序版本。它们中的大多数与您的相似。但是,要增加并行度,必须最小化全局队列中的争用(在您的代码中为s)。因此,可能会有一些优化,例如具有本地队列。尽管算法本身是完全并行的,但是实现可能需要同步工件。而且,最重要的是,很难实现加速。

但是,您仍然可以通过两种方式在OpenMP中直接使用递归并行性:(1)限制线程总数,以及(2)使用OpenMP 3.0的task

这是第一种方法的伪代码(这仅基于OmpSCR的基准):

void qsort_omp_recursive(int* begin, int* end)
{
if (begin != end) {
// Partition ...

// Throttling
if (...) {
qsort_omp_recursive(begin, middle);
qsort_omp_recursive(++middle, ++end);
} else {

#pragma omp parallel sections nowait
{
#pragma omp section
qsort_omp_recursive(begin, middle);
#pragma omp section
qsort_omp_recursive(++middle, ++end);
}
}
}
}

为了运行此代码,您需要调用 omp_set_nested(1)omp_set_num_threads(2)。代码真的很简单。我们只是在工作划分上产生了两个线程。但是,我们插入了一个简单的限制逻辑,以防止线程过多。请注意,我的实验显示了这种方法的不错的提速。

最后,您可以使用OpenMP 3.0的 task,其中任务是逻辑上并发的工作。在上述所有OpenMP方法中,每个并行构造都产生两个物理线程。您可能会说,任务与工作线程之间存在一对一的硬映射。但是, task将逻辑任务和工作程序分开。

因为OpenMP 3.0还不流行,所以我将使用Cilk Plus,它非常适合表示这种嵌套的和递归的并行性。在Cilk Plus中,并行化非常容易:
void qsort(int* begin, int* end)
{
if (begin != end) {
--end;
int* middle = std::partition(begin, end,
std::bind2nd(std::less<int>(), *end));
std::swap(*end, *middle);

cilk_spawn qsort(begin, middle);
qsort(++middle, ++end);
// cilk_sync; Only necessay at the final stage.
}
}

我从Cilk Plus的示例代码中复制了此代码。您将看到一个关键字 cilk_spawn是并行化quicksort的所有内容。我跳过了Cilk Plus和spawn关键字的说明。但是,这很容易理解:两个递归调用被声明为逻辑上并发的任务。每当进行递归时,就会创建逻辑任务。但是,Cilk Plus运行时(实现了有效的工作窃取调度程序)将处理各种脏工作。它以最佳方式将并行任务排队并映射到工作线程。

请注意,OpenMP 3.0的 task本质上类似于Cilk Plus的方法。我的实验表明,相当不错的加速是可行的。我在8核计算机上的速度提高了3到4倍。而且,加速是规模化的。 Cilk Plus的绝对加速比OpenMP 3.0的绝对加速大。

Cilk Plus(和OpenMP 3.0)的方法与您的方法基本相同:并行任务和工作负载分配的分离。但是,很难有效实现。例如,您必须减少争用并使用无锁数据结构。

关于c++ - C OpenMP并行quickSort,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8023135/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com