【问题标题】:Reducing on array in OpenMP减少 OpenMP 中的数组
【发布时间】:2013-12-23 05:16:22
【问题描述】:

我正在尝试并行化以下程序,但不知道如何减少数组。我知道这样做是不可能的,但有其他选择吗?谢谢。 (我在 m 上添加了减法,这是错误的,但想就如何做得到建议。)

#include <iostream>
#include <stdio.h>
#include <time.h>
#include <omp.h>
using namespace std;

int main ()
{
  int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
  int S [10];

  time_t start_time = time(NULL);
  #pragma omp parallel for private(m) reduction(+:m)
  for (int n=0 ; n<10 ; ++n ){
    for (int m=0; m<=n; ++m){
      S[n] += A[m];
    }
  }
  time_t end_time = time(NULL);
  cout << end_time-start_time;

  return 0;
}

【问题讨论】:

    标签: c++ multithreading parallel-processing openmp reduction


    【解决方案1】:

    是的,可以使用 OpenMP 进行数组缩减。在 Fortran 中,它甚至为此进行了构造。在 C/C++ 中,你必须自己做。这里有两种方法。

    第一种方法为每个线程制作S的私有版本,并行填充,然后在临界区将它们合并到S中(见下面的代码)。第二种方法创建一个维度为 10*nthreads 的数组。并行填充此数组,然后将其合并到S 中,而不使用临界区。第二种方法要复杂得多,如果您不小心,可能会出现缓存问题,尤其是在多插槽系统上。更多详情请看Fill histograms (array reduction) in parallel with OpenMP without using a critical section

    第一种方法

    int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
    int S [10] = {0};
    #pragma omp parallel
    {
        int S_private[10] = {0};
        #pragma omp for
        for (int n=0 ; n<10 ; ++n ) {
            for (int m=0; m<=n; ++m){
                S_private[n] += A[m];
            }
        }
        #pragma omp critical
        {
            for(int n=0; n<10; ++n) {
                S[n] += S_private[n];
            }
        }
    }
    

    第二种方法

    int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
    int S [10] = {0};
    int *S_private;
    #pragma omp parallel
    {
        const int nthreads = omp_get_num_threads();
        const int ithread = omp_get_thread_num();
    
        #pragma omp single 
        {
            S_private = new int[10*nthreads];
            for(int i=0; i<(10*nthreads); i++) S_private[i] = 0;
        }
        #pragma omp for
        for (int n=0 ; n<10 ; ++n )
        {
            for (int m=0; m<=n; ++m){
                S_private[ithread*10+n] += A[m];
            }
        }
        #pragma omp for
        for(int i=0; i<10; i++) {
            for(int t=0; t<nthreads; t++) {
                S[i] += S_private[10*t + i];
            }
        }
    }
    delete[] S_private;
    

    【讨论】:

      【解决方案2】:

      关于 Zboson 的回答,我有两点意见:
      1. 方法 1 肯定是正确的,但归约循环实际上是串行运行的,因为 #pragma omp critical 这当然是必要的,因为部分矩阵对于每个线程都是本地的,并且相应的归约必须由该矩阵的线程完成。
      2. 方法2:初始化循环可以移到单个部分之外,因此可以并行化。

      以下程序实现数组缩减使用 openMP v4.0 用户定义缩减工具

      /* Compile with:
           gcc -Wall -fopenmp -o ar ar.c
         Run with:
           OMP_DISPLAY_ENV=TRUE OMP_NUM_THREADS=10 OMP_NESTED=TRUE ./ar
      */
      #include <stdio.h>
      #include <omp.h>
      struct m10x1 {int v[10];};
      int A [] =       {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};  
      struct m10x1 S = {{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
      int n,m=0;
      
      void print_m10x1(struct m10x1 x){
        int i;
        for(i=0;i<10;i++) printf("%d ",x.v[i]);
        printf("\n");
      }
      
      struct m10x1 add_m10x1(struct m10x1 x,struct m10x1 y){
        struct m10x1 r ={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
        int i;
        for (i=0;i<10;i++) r.v[i]=x.v[i]+y.v[i];
        return r;
      }
      
      #pragma omp declare reduction(m10x1Add: struct m10x1: \
      omp_out=add_m10x1(omp_out, omp_in)) initializer( \
      omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )
      
      int main ()
      {
        #pragma omp parallel for reduction(m10x1Add: S)
        for ( n=0 ; n<10 ; ++n )
          {
            for (m=0; m<=n; ++m){
              S.v[n] += A[m];
            }
          }
        print_m10x1(S);
      }
      

      这逐字遵循OpenMP 4.0 features 第 97 页上的复数减少示例。

      虽然并行版本可以正常工作,但可能存在性能问题,我没有调查:

      1. add_m10x1 输入和输出按值传递。
      2. add_m10x1 中的循环是串行运行的。

      所说的“性能问题”是我自己造成的,不介绍它们是完全直截了当的:

      1. add_m10x1 的参数应该通过引用传递(通过 C 中的指针,C++ 中的引用)
      2. add_m10x1 中的计算应该就地完成。
      3. add_m10x1 应声明为无效并删除返回语句。结果通过第一个参数返回。
      4. 应相应修改 declare reduction 杂注,组合器应只是函数调用而不是赋值(v4.0 规范 p181 第 9,10 行)。
      5. add_m10x1 中的 for 循环可以通过 omp parallel for pragma 并行化
      6. 应启用并行嵌套(例如通过 OMP_NESTED=TRUE)

      那么代码的修改部分是:

      void add_m10x1(struct m10x1 * x,struct m10x1 * y){
        int i;
        #pragma omp parallel for
        for (i=0;i<10;i++) x->v[i] += y->v[i];
      }
      
      #pragma omp declare reduction(m10x1Add: struct m10x1: \
      add_m10x1(&omp_out, &omp_in)) initializer( \
      omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )
      

      【讨论】:

        【解决方案3】:

        由于没有提到其他答案,所以我添加了这个答案。

        我正在尝试并行化以下程序,但不知道如何 减少数组。我知道这样做是不可能的,但是有 > 替代方案吗?

        使用OpenMP 4.5,您可以使用pragma 减少数组,即:

        #pragma omp parallel for reduction(+:S)
        

        一个完整的运行示例:

        #define S_SIZE 10
        #include <stdio.h>
        #include <time.h>
        #include <omp.h>
        int main ()
        {
          int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
          int S [S_SIZE] = {0};
        
          #pragma omp parallel for reduction(+:S[:S_SIZE])
          for (int n=0 ; n<S_SIZE ; ++n ){
            for (int m=0; m<=n; ++m){
              S[n] += A[m];
            }
          }
          int expected_output [] = {84, 114, 209, 303, 339, 412, 464, 487, 489, 502};   
          for(int i = 0; i < S_SIZE; i++){
              if(S[i] == expected_output[i])
                printf("%d\n", S[i]);
             else
               printf("ERROR! it should have been %d instead of %d\n", expected_output[i], S[i]);
          }
          
          return 0;
        }
        

        输出:

        84
        114
        209
        303
        339
        412
        464
        487
        489
        502
        

        【讨论】:

          【解决方案4】:

          使用并行循环,每个线程将根据调度程序处理给定的循环索引子集。然后数组 S 不需要归约,因为每个索引 n 将为外部循环独立处理。同样应该不存在竞争条件的问题,因为每个线程将写入不同的位置 S[n]。所以上面的代码只使用指令就可以正常工作

          #pragma omp parallel for

          对于外循环。

          【讨论】:

            【解决方案5】:

            如果将您的代码翻译成可以在 OpenMP 缩减操作中使用数组的 Fortran 没有吸引力,您可以使用一堆临时变量。例如

            int S0, S1, S2, ..., S9;
            ...
            #pragma omp parallel for private(...) shared(S0, S1, S2, ..., S9) \
                        reduction(+:S0, S1, S2, ..., S9)
            for ...
            

            这使您不得不编写某种ifcase 语句来确定要更新哪些临时文件,这让您没有吸引力。如果您的代码只是您想用于学习的示例,请继续。

            但是,如果您真的打算编写一个并行前缀求和例程,那么请四处寻找。 This is a good place to start.

            【讨论】:

              猜你喜欢
              • 2014-09-12
              • 1970-01-01
              • 1970-01-01
              • 2021-04-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-03-22
              相关资源
              最近更新 更多