Dependencies OpenMP中的并行累积(前缀)和:线程之间的值通信

Dependencies OpenMP中的并行累积(前缀)和:线程之间的值通信,dependencies,sum,openmp,Dependencies,Sum,Openmp,假设我有一个函数f(I),它依赖于索引I(以及其他无法预计算的值)。 我想填充一个数组a,以便a[n]=sum(f(I))从I=0到n-1 编辑:在赫里斯托·伊利耶夫发表评论后,我意识到我现在所做的是一件好事 这可以用以下代码编写: float sum = 0; for(int i=0; i<N; i++) { sum += f(i); a[i] = sum; } 但是,不使用OpenMP的非并行循环也可以做到这一点。然而,我所提出的解决方案是复杂的,也许还不够成熟。因此

假设我有一个函数
f(I)
,它依赖于索引
I
(以及其他无法预计算的值)。 我想填充一个数组
a
,以便
a[n]=sum(f(I))从I=0到n-1

编辑:在赫里斯托·伊利耶夫发表评论后,我意识到我现在所做的是一件好事

这可以用以下代码编写:

float sum = 0;
for(int i=0; i<N; i++) {
    sum += f(i);
    a[i] = sum;
}
但是,不使用OpenMP的非并行循环也可以做到这一点。然而,我所提出的解决方案是复杂的,也许还不够成熟。因此,我的问题是,是否有一种更简单、更简单的方法来使用OpenMP实现这一点

下面的代码基本上运行我为每个线程列出的第一个代码。结果是给定线程中
a
的值在一个常量内是正确的。我将每个线程的总和保存到带有
nthreads+1
元素的数组
suma
。这允许我在线程之间通信,并确定每个线程的恒定偏移量。然后我用偏移量校正
a[I]
的值

float *suma;
#pragma omp parallel
{
    const int ithread = omp_get_thread_num();
    const int nthreads = omp_get_num_threads();
    const int start = ithread*N/nthreads;
    const int finish = (ithread+1)*N/nthreads;
    #pragma omp single
    {
        suma = new float[nthreads+1];
        suma[0] = 0;
    }
    float sum = 0;
    for (int i=start; i<finish; i++) {
        sum += f(i);
        a[i] = sum;
    }
    suma[ithread+1] = sum;
    #pragma omp barrier
    float offset = 0;
    for(int i=0; i<(ithread+1); i++) {
        offset += suma[i];
    }
    for(int i=start; i<finish; i++) {
        a[i] += offset;
    }
}
delete[] suma;
float*suma;
#pragma-omp并行
{
const int ithread=omp_get_thread_num();
const int nthreads=omp_get_num_threads();
const int start=ithread*N/nthreads;
常量int finish=(ithread+1)*N/N次读取;
#布拉格omp单曲
{
suma=新浮点[n次读取+1];
suma[0]=0;
}
浮点数和=0;

对于(int i=start;i您可以将策略扩展到任意数量的子区域,并使用任务递归地减少它们:

#include<vector>
#include<iostream>

using namespace std;

const int n          = 10000;
const int baseLength = 100;

int f(int ii) {
  return ii;
}

int recursiveSumBody(int * begin, int * end){

  size_t length  = end - begin;
  size_t mid     = length/2;
  int    sum     = 0;


  if ( length < baseLength ) {
    for(size_t ii = 1; ii < length; ii++ ){
        begin[ii] += begin[ii-1];
    }
  } else {
#pragma omp task shared(sum)
    {
      sum = recursiveSumBody(begin    ,begin+mid);
    }
#pragma omp task
    {
      recursiveSumBody(begin+mid,end      );
    }
#pragma omp taskwait

#pragma omp parallel for
    for(size_t ii = mid; ii < length; ii++) {
      begin[ii] += sum;
    }

  }
  return begin[length-1];
}

void recursiveSum(int * begin, int * end){

#pragma omp single
  {
    recursiveSumBody(begin,end);
  }    
}


int main() {

  vector<int> a(n,0);

#pragma omp parallel
  {
    #pragma omp for
    for(int ii=0; ii < n; ii++) {          
      a[ii] = f(ii);
    }  

    recursiveSum(&a[0],&a[n]);

  }
  cout << n*(n-1)/2 << endl;
  cout << a[n-1] << endl;

  return 0;
}
#包括
#包括
使用名称空间std;
常数n=10000;
常数int baseLength=100;
内部f(内部ii){
回报二;
}
int recursiveSumBody(int*begin,int*end){
长度=结束-开始;
尺寸=长度/2;
整数和=0;
if(长度<基准长度){
用于(尺寸=1;ii<长度;ii++){
开始[ii]+=开始[ii-1];
}
}否则{
#pragma omp任务共享(总和)
{
sum=递归SumBody(开始,开始+中间);
}
#pragma-omp任务
{
递归SumBody(开始+中间,结束);
}
#pragma omp taskwait
#pragma-omp并行
用于(尺寸=中间;ii<长度;ii++){
开始[ii]+=和;
}
}
返回开始[length-1];
}
void recursiveSum(int*begin,int*end){
#布拉格omp单曲
{
递归SumBody(开始、结束);
}    
}
int main(){
向量a(n,0);
#pragma-omp并行
{
#pragma omp for
对于(intii=0;iicout为完整起见,考虑到Hristo的评论,我添加了OP的MWE代码:

#包括
#包括
使用std::cout;
使用std::endl;
常数int N=10;
常数int Nthr=4;
浮点f(inti){返回(浮点)i;}
内部主(空){
omp_设置_数量_线程(Nthr);
浮动*a=新浮动[N];
浮动*suma=新浮动[Nthr+1];
suma[0]=0.0;
浮动总和=0.0;
#pragma omp并行计划(静态)firstprivate(总和)

for(int i=0;这与OpenMP通常计算前缀和的方式差不多。您可以将
#pragma omp for schedule(static)
应用于运行在
a[]上的两个循环,而不是手动计算开始和结束索引
@HristoIliev,我认为尽管在实践中OpenMP像我一样定义了开始和结束,但我不应该假设OpenMP会这样做(我想我在你的一篇文章中读到了这一点)
在某些条件下(在您的情况下满足这些条件),具有标准可重复分布模式所保证的特殊属性.好的,我想我明白了。我提出了一个如此多的问题,因为我认为这是其他人可能想知道的。我已经有一段时间不确定了。非常感谢你发布了一个工作示例!我想我希望得到一个适用于OpenMP 2.0的答案(这样它也适用于MSVC)但是这对我来说是一个使用OpenMP任务的好机会。我必须增加
baseLength
,以获得
n=10000
的正确值。你知道这种方法有多有效吗?嗯,我不认为对于这个特定的示例任务会比你编写的代码更快。我更担心的是你必须增加
baseLength
以获得正确的值,这意味着某个地方存在缺陷。无论如何,我无法在程序中看到数据竞争。看来
baseLength
必须等于
n
才能获得正确的结果。我在我的机器上为任何
baseLength
获得正确的结果。Comp我读过
g++4.8.1
。奇怪的是,我不知道,我必须包括编译,但就是这样。我使用的是g++4.7.3。
#include<vector>
#include<iostream>

using namespace std;

const int n          = 10000;
const int baseLength = 100;

int f(int ii) {
  return ii;
}

int recursiveSumBody(int * begin, int * end){

  size_t length  = end - begin;
  size_t mid     = length/2;
  int    sum     = 0;


  if ( length < baseLength ) {
    for(size_t ii = 1; ii < length; ii++ ){
        begin[ii] += begin[ii-1];
    }
  } else {
#pragma omp task shared(sum)
    {
      sum = recursiveSumBody(begin    ,begin+mid);
    }
#pragma omp task
    {
      recursiveSumBody(begin+mid,end      );
    }
#pragma omp taskwait

#pragma omp parallel for
    for(size_t ii = mid; ii < length; ii++) {
      begin[ii] += sum;
    }

  }
  return begin[length-1];
}

void recursiveSum(int * begin, int * end){

#pragma omp single
  {
    recursiveSumBody(begin,end);
  }    
}


int main() {

  vector<int> a(n,0);

#pragma omp parallel
  {
    #pragma omp for
    for(int ii=0; ii < n; ii++) {          
      a[ii] = f(ii);
    }  

    recursiveSum(&a[0],&a[n]);

  }
  cout << n*(n-1)/2 << endl;
  cout << a[n-1] << endl;

  return 0;
}