繁体   English   中英

C OpenMP并行quickSort

[英]C OpenMP parallel quickSort

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

码:

#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是第1行包含100000的文件,后面是以空格分隔的下一行的100k个整数。

我在linux上使用gcc 4.5.2

谢谢您的帮助,

我实际上没有运行你的代码,但是我发现p上有一个错误,它应该是private而不是shared 并行调用qsqs(v, p.first, p.second); 将在p上进行比赛,导致不可预测的行为。 qs的局部变量应该没问题,因为所有线程都有自己的堆栈。 但是,整体方法很好。 你走在正确的轨道上。


以下是我对并行快速排序实施的一般性评论。 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是并行化快速排序的一切。 我正在跳过Cilk Plus的解释并生成关键字。 但是,它很容易理解:两个递归调用被声明为逻辑并发任务。 每当递归发生时,都会创建逻辑任务。 但是,Cilk Plus运行时(实现高效的工作窃取调度程序)将处理各种脏工作。 它最佳地将并行任务排队并映射到工作线程。

请注意,OpenMP 3.0的task基本上类似于Cilk Plus的方法。 我的实验表明,相当不错的加速是可行的。 我在8核机器上获得了3~4倍的加速。 而且,加速是规模。 Cilk Plus的绝对加速比OpenMP 3.0更高。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM