【问题标题】:OpenMP : Parallel QuickSortOpenMP:并行快速排序
【发布时间】:2013-04-07 02:56:12
【问题描述】:

我尝试使用 OpenMP 在分区部分和 QuickSort 部分中并行化 QuickSort。我的C代码如下:

#include "stdlib.h"
#include "stdio.h"
#include "omp.h"

// parallel partition
int ParPartition(int *a, int p, int r) {
    int b[r-p];
    int key = *(a+r); // use the last element in the array as the pivot
    int lt[r-p]; // mark 1 at the position where its element is smaller than the key, else 0
    int gt[r-p]; // mark 1 at the position where its element is bigger than the key, else 0
    int cnt_lt = 0; // count 1 in the lt array
    int cnt_gt = 0; // count 1 in the gt array
    int j=p;
    int k = 0; // the position of the pivot
    // deal with gt and lt array
    #pragma omp parallel for
    for ( j=p; j<r; ++j) {
        b[j-p] = *(a+j);
        if (*(a+j) < key) {
            lt[j-p] = 1;
            gt[j-p] = 0;
        } else {
            lt[j-p] = 0;
            gt[j-p] = 1;
        }
    }
    // calculate the new position of the elements
    for ( j=0; j<(r-p); ++j) {
        if (lt[j]) {
            ++cnt_lt;
            lt[j] = cnt_lt;
        } else
            lt[j] = cnt_lt;
        if (gt[j]) {
            ++cnt_gt;
            gt[j] = cnt_gt;
        } else
            gt[j] = cnt_gt;
    }
    // move the pivot
    k = lt[r-p-1];
    *(a+p+k) = key;
    // move elements to their new positon
    #pragma omp parallel for 
    for ( j=p; j<r; ++j) {
        if (b[j-p] < key)
            *(a+p+lt[j-p]-1) = b[j-p];
        else if (b[j-p] > key)
            *(a+k+gt[j-p]) = b[j-p];
    }
    return (k+p);
}

void ParQuickSort(int *a, int p, int r) {
    int q;
    if (p<r) {
        q = ParPartition(a, p, r);
        #pragma omp parallel sections
        {
        #pragma omp section
        ParQuickSort(a, p, q-1);
        #pragma omp section
        ParQuickSort(a, q+1, r);
        }
    }
}

int main() {
    int a[10] = {5, 3, 8, 4, 0, 9, 2, 1, 7, 6};
    ParQuickSort(a, 0, 9);
    int i=0;
    for (; i!=10; ++i)
        printf("%d\t", a[i]);
    printf("\n");
    return 0;
}

对于main函数中的例子,排序结果为:

0   9   9   2   2   2   6   7   7   7

我使用 gdb 进行调试。在早期的递归中,一切顺利。但是在某些递归中,它突然搞砸了,开始重复元素。然后生成上面的结果。

谁能帮我找出问题所在?

【问题讨论】:

    标签: c multithreading parallel-processing openmp quicksort


    【解决方案1】:

    我决定发布这个答案是因为:

    1. the accepted answer is wrong,并且这些天用户似乎不活跃。在

      上有一个竞态条件
       #pragma omp parallel for
       for(i = p; i < r; i++){
           if(a[i] < a[r]){
               lt[lt_n++] = a[i]; //<- race condition lt_n is shared
           }else{
               gt[gt_n++] = a[i];  //<- race condition gt_n is shared
           }   
       }   
      
    2. 尽管如此,即使它是正确的,这个问题的现代答案是使用 OpenMP tasks 而不是 sections

    我正在向社区提供这种方法的完整可运行示例,包括测试和分析。

    #include <assert.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <omp.h>
    
    #define TASK_SIZE 100
    
    unsigned int rand_interval(unsigned int min, unsigned int max)
    {
        // https://stackoverflow.com/questions/2509679/
        int r;
        const unsigned int range = 1 + max - min;
        const unsigned int buckets = RAND_MAX / range;
        const unsigned int limit = buckets * range;
    
        do
        {
            r = rand();
        } 
        while (r >= limit);
    
        return min + (r / buckets);
    }
    
    void fillupRandomly (int *m, int size, unsigned int min, unsigned int max){
        for (int i = 0; i < size; i++)
        m[i] = rand_interval(min, max);
    } 
    
    
    void init(int *a, int size){
       for(int i = 0; i < size; i++)
           a[i] = 0;
    }
    
    void printArray(int *a, int size){
       for(int i = 0; i < size; i++)
           printf("%d ", a[i]);
       printf("\n");
    }
    
    int isSorted(int *a, int size){
       for(int i = 0; i < size - 1; i++)
          if(a[i] > a[i + 1])
            return 0;
       return 1;
    }
    
    
    int partition(int * a, int p, int r)
    {
        int lt[r-p];
        int gt[r-p];
        int i;
        int j;
        int key = a[r];
        int lt_n = 0;
        int gt_n = 0;
    
        for(i = p; i < r; i++){
            if(a[i] < a[r]){
                lt[lt_n++] = a[i];
            }else{
                gt[gt_n++] = a[i];
            }   
        }   
    
        for(i = 0; i < lt_n; i++){
            a[p + i] = lt[i];
        }   
    
        a[p + lt_n] = key;
    
        for(j = 0; j < gt_n; j++){
            a[p + lt_n + j + 1] = gt[j];
        }   
    
        return p + lt_n;
    }
    
    void quicksort(int * a, int p, int r)
    {
        int div;
    
        if(p < r){ 
            div = partition(a, p, r); 
            #pragma omp task shared(a) if(r - p > TASK_SIZE) 
            quicksort(a, p, div - 1); 
            #pragma omp task shared(a) if(r - p > TASK_SIZE)
            quicksort(a, div + 1, r); 
        }
    }
    
    int main(int argc, char *argv[])
    {
            srand(123456);
            int N  = (argc > 1) ? atoi(argv[1]) : 10;
            int print = (argc > 2) ? atoi(argv[2]) : 0;
            int numThreads = (argc > 3) ? atoi(argv[3]) : 2;
            int *X = malloc(N * sizeof(int));
            int *tmp = malloc(N * sizeof(int));
    
            omp_set_dynamic(0);              /** Explicitly disable dynamic teams **/
            omp_set_num_threads(numThreads); /** Use N threads for all parallel regions **/
    
             // Dealing with fail memory allocation
            if(!X || !tmp)
            { 
               if(X) free(X);
               if(tmp) free(tmp);
               return (EXIT_FAILURE);
            }
    
            fillupRandomly (X, N, 0, 5);
    
            double begin = omp_get_wtime();
            #pragma omp parallel
            {
                #pragma omp single
                 quicksort(X, 0, N);
            }   
            double end = omp_get_wtime();
            printf("Time: %f (s) \n",end-begin);
        
            assert(1 == isSorted(X, N));
    
            if(print){
               printArray(X, N);
            }
    
            free(X);
            free(tmp);
            return (EXIT_SUCCESS);
    
        return 0;
    }
    

    如何运行:

    这个程序接受三个参数:

    1. 数组的大小;
    2. 是否打印数组,0表示否,否则是;
    3. 并行运行的线程数。

    迷你基准测试

    在 4 核机器中:输入 100000 with

    1 Thread  -> Time: 0.784504 (s)
    2 Threads -> Time: 0.424008 (s) ~ speedup 1.85x
    4 Threads -> Time: 0.282944 (s) ~ speedup 2.77x
    

    【讨论】:

      【解决方案2】:

      我已经在生产环境中实现了并行快速排序,尽管使用并发进程(即 fork() 和 join())而不是 OpenMP。我还找到了一个相当不错的 pthread 解决方案,但就最坏情况的运行时间而言,并发进程解决方案是最好的。首先让我说,您似乎没有为每个线程制作输入数组的副本,因此您肯定会遇到可能破坏数据的竞争条件。

      本质上,发生的事情是您在共享内存中创建了一个数组N,当您执行#pragma omp parallel sections 时,您将产生与#pragma omp section 一样多的工作线程。 s。每次工作线程试图访问和修改 a 的元素时,它都会执行一系列指令:“从给定地址读取 N 的第 n 个值”、“修改第 n 个N 的值”,“将 N 的第 n 个值写回给定地址”。由于您有多个没有锁定或同步的线程,读取、修改和写入指令可以由多个处理器以任意顺序执行,因此线程可能会覆盖彼此的修改或读取未更新的值。

      我发现的最佳解决方案(经过数周的测试和基准测试我提出的许多解决方案)是将列表细分 log(n) 次,其中 n em> 是处理器的数量。例如,如果您有一台四核机器 (n = 4),请将列表细分 2 次 (log(4) = 2),选择作为数据集中位数的枢轴。重要的是枢轴是中位数,否则您可能会遇到选择不当的枢轴导致列表在进程之间分布不均的情况。然后每个进程对其本地子数组进行快速排序,然后将其结果与其他进程的结果合并。这被称为“hyperquicksort”,从最初的 github 搜索中,我找到了this。我不能保证那里的代码,也不能发布我编写的任何代码,因为它受 NDA 保护。

      顺便说一句,最好的并行排序算法之一是 PSRS(通过常规采样并行排序),它使列表大小在进程之间更加平衡,不会在进程之间不必要地通信密钥,并且可以处理任意数量的并发进程(它们不一定是 2 的幂)。

      【讨论】:

      • 这似乎是错误的。该算法将要排序的数组划分为初始列表的较小部分列表。只要生成的线程只是在它们“自己的”部分列表中交换对象,就不会出现竞争条件。
      【解决方案3】:

      我的第一个评论很抱歉。和你的问题无关。我没有找到你问题的真正问题(可能你的移动元素有问题)。根据你的意见,我写了一个类似的程序,有用 很好。(我也是 OpenMP 的新手)。

      #include <stdio.h>
      #include <stdlib.h>
      
      
      int partition(int * a, int p, int r)
      {
          int lt[r-p];
          int gt[r-p];
          int i;
          int j;
          int key = a[r];
          int lt_n = 0;
          int gt_n = 0;
      
      #pragma omp parallel for
          for(i = p; i < r; i++){
              if(a[i] < a[r]){
                  lt[lt_n++] = a[i];
              }else{
                  gt[gt_n++] = a[i];
              }   
          }   
      
          for(i = 0; i < lt_n; i++){
              a[p + i] = lt[i];
          }   
      
          a[p + lt_n] = key;
      
          for(j = 0; j < gt_n; j++){
              a[p + lt_n + j + 1] = gt[j];
          }   
      
          return p + lt_n;
      }
      
      void quicksort(int * a, int p, int r)
      {
          int div;
      
          if(p < r){ 
              div = partition(a, p, r); 
      #pragma omp parallel sections
              {   
      #pragma omp section
                  quicksort(a, p, div - 1); 
      #pragma omp section
                  quicksort(a, div + 1, r); 
      
              }
          }
      }
      
      int main(void)
      {
          int a[10] = {5, 3, 8, 4, 0, 9, 2, 1, 7, 6};
          int i;
      
          quicksort(a, 0, 9);
      
          for(i = 0;i < 10; i++){
              printf("%d\t", a[i]);
          }
          printf("\n");
          return 0;
      }
      

      【讨论】:

      • 谢谢!我猜是因为 b 数组?
      • @randomp,我只能说“也许”。:-)
      • 我看不出这会起作用的原因。在parallel for中,lt_ngt_n可能被多个线程修改而没有任何同步。也许数组太小以至于只有一个线程在该部分上工作。
      • 更新: 我运行了多次,确实看到了错误的结果:0 1 2 3 5 6 6 7 8 9。因此代码错误。 @随机数
      • @ftfish 是的,此代码似乎错误,如果您想检查,我已经发布了解决方案
      猜你喜欢
      • 1970-01-01
      • 2019-12-22
      • 1970-01-01
      • 1970-01-01
      • 2021-03-04
      • 1970-01-01
      • 2012-11-28
      • 2021-06-06
      • 2010-10-04
      相关资源
      最近更新 更多