2

一个简单的 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来存储来自主线程的修改值。

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

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

非常感谢。

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

4

3 回答 3

0

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

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

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

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

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

顺便说一句:你说

在平行区域之外,var得到它的原始值,0。

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

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

于 2014-05-28T15:04:11.053 回答
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);
}
于 2013-04-19T14:32:18.070 回答
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

于 2014-05-28T17:38:37.783 回答