减少 OpenMP 中的数组

Posted

技术标签:

【中文标题】减少 OpenMP 中的数组【英文标题】:Reducing on array in 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;

【问题讨论】:

【参考方案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 页上的复数减少示例。

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

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

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

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

【讨论】:

以上是关于减少 OpenMP 中的数组的主要内容,如果未能解决你的问题,请参考以下文章

是否可以使用 openmp 对数组进行缩减?

SSE/AVX + OpenMP:数组的快速求和

如何处理 OpenMP 中的数据竞争?

OpenMP:堆数组的性能不佳(堆栈数组工作正常)

使用 OpenMP 并行化 C 中的基数排序

C ++中OpenMP中的有序线程ID