如何在并行区域外维护变量值?

时间:2013-04-19 00:37:14

标签: c++ c openmp

一个简单的OpenMP程序:

#include <omp.h>
#include <iostream>

int main() {
  int var = 0;
  int var2 = 0;
  #pragma omp parallel private(var) shared(var2)
  {
    var = 1;
    int tid = omp_get_thread_num();
    printf("Inside the parallel region, var gets modified by thread #%d to %d\n",tid,var);
    if(tid == 0)
      var2 = var;
  }
  printf("Outside the parallel region, var  = %d\n", var);
  printf("Outside the parallel region, var2 = %d\n", var2);
}

结果:

Inside the parallel region, var gets modified by thread #3 to 1
Inside the parallel region, var gets modified by thread #0 to 1
Inside the parallel region, var gets modified by thread #6 to 1
Inside the parallel region, var gets modified by thread #1 to 1
Inside the parallel region, var gets modified by thread #5 to 1
Inside the parallel region, var gets modified by thread #7 to 1
Inside the parallel region, var gets modified by thread #2 to 1
Inside the parallel region, var gets modified by thread #4 to 1
Outside the parallel region, var  = 0
Outside the parallel region, var2 = 1

我想要做的是将var的值设置为并行区域内的最后修改值。

由于它不是#pragma omp for循环,因此使用lastprivate无效。

在并行区域之外,var获取其原始值0。一个技巧是使用共享变量var2来存储来自主线程的修改后的值。

但是这会增加开销并且似乎不是一种优雅的方法,并且如果我想获得由 last 线程修改的值,而不是主机,(例如,找出哪个线程最后完成),然后这个技巧将不起作用。

我对OpenMP很新,所以我可能会遗漏一些东西。如果我不是,有没有办法克服这个棘手的事情?

非常感谢。

编辑:我的问题是如何在并行区域完成后保留私有变量的最后一个值。或者,如果您可以解释为什么lastprivate在概念上无法在#pragma omp parallel中使用,我会将其视为完美答案。

3 个答案:

答案 0 :(得分:0)

要找出最后完成的线程,请让每个线程将其完成时间写入数组。该数组的大小至少应为omp_get_max_threads()。使用并行区域内的omp_get_thread_num()对其进行索引。

代码离开并行区域后,找到数组中的最大值。

理想情况下,数组应该对齐并填充,以便每个元素都在一个单独的缓存行中,这样线程在编写完成时间时不必传递共享缓存行。

如果并行区域位于程序的外层,那么通过利用线程私有变量在顶层并行区域之间保留其值的事实,还有另一种更微妙的方法可以做到这一点。 。以下是如何利用这一技巧的示例。

#include <omp.h>
#include <stdio.h>
#include <unistd.h>

double tmp;
#pragma omp threadprivate(tmp)

int main() {
    double start = omp_get_wtime();
#pragma omp parallel
    {
        sleep(1);
        tmp = omp_get_wtime();
    }
    double finish=start;
#pragma omp parallel reduction(max:finish)
    {
        if( tmp>finish ) finish = tmp;
    }
    printf("last thread took time = %g\n",finish-start);
}

答案 1 :(得分:0)

我认为可能存在对lastprivate条款的误解。 OpenMP标准(4.0,第2.14.3.5节)说:

  

[...]当 lastprivate 子句出现在指令上,用于标识工作共享构造[...]时,每个新列表项的值来自相关循环的顺序最后一次迭代,或词汇最后的部分构造,被分配给原始列表项。

这里,术语“列表项”是指您在lastprivate子句中传递的变量。因此,在每种情况下,分配给原始变量(声明为lastprivate)的值都会或多或少地被确定。在循环的情况下,分配给变量的值将与在相应顺序循环的最后一次迭代中分配的值相同。对于节,它是最后一节中变量的最后一个赋值。这也是你对串行程序的期望,所以我认为很容易看出改变这些语义没有意义。

另一方面,如果允许lastprivate用于与工作共享(或SIMD)构造不同的东西,如在您的示例中,则会破坏这些语义。因为您无法事先知道哪个线程将首先完成,所以您最终会在每次执行时最终都会发生变化(如果您愿意,可以将其称为非确定性甚至未定义)。与上一段中所述的行为相反,这很可能不是您对串行程序的期望。 我希望这可以回答你关于非工作共享结构中缺少lastprivate的问题。

关于您的示例,我认为OpenMP中没有任何内置功能可以实现您想要实现的功能。但由于我对这个话题也很新,我不想最终确定这个。

顺便说一下:你说

  

在并行区域之外,var获取其原始值0。

这可能是您的OpenMP实施的结果。但是一般来说,在对此变量具有private子句的并行区域之后,原始变量的值是未定义的。所以我不认为这是理所当然的。

我希望这能回答你的问题。

答案 2 :(得分:0)

每个线程都有自己的私有变量。 private构造只会引起混淆。如果你想要一组线程中的最后一个私有值,那么只为每个线程返回一个私有值。你可以这样做:

int *vala;
int nthreads;
#pragma omp parallel
{
    nthreads = omp_get_num_threads();
    int ithread = omp_get_thread_num();
    #pragma omp single
    vala = new int[nthreads];
    //
    vala[ithread] = ithread;
}
//vala[] = 0,1,2,3,4,5,6,7,8
delete[] vala;

但一般来说,自己分配内存是个坏主意。您应该让每个线程为自己的私有变量分配内存。问题是上面的代码在高速缓存行左(64字节)和页面级(4096字节)都有错误共享。解决此问题的一种方法是不在并行循环中写入vala,而是仅为每个线程写入它。例如

int *vala;
int nthreads;
#pragma omp parallel
{
    int nthreads = omp_get_num_threads();
    int ithread = omp_get_thread_num();
    #pragma omp single
    vala = new int[nthreads];
    int val = 0;
    #pragma omp for
    for(int i=0; i<n; i++) {
        val = i;    
    }
    vala[ithread] = val;
}

仍然存在错误共享,但效果无关紧要,因为它是按线程完成的,而不是每次迭代。

在去年使用OpenMP时,我想不出一次我需要知道最后一个线程退出并行部分的时间。但是,订单很重要。例如,当一个操作是关联的但不是可交换的时(例如一系列矩阵乘法)。在这种情况下,您可以将数组填充为线程数的函数,并依赖于具有静态调度的块按照线程数C++ OpenMP: Split for loop in even chunks static and join data at the end递增的顺序分配。