0

我正在尝试创建一个程序来创建一个数组,并使用 OpenMP 将值分配给该数组中的每个位置。这将是微不足道的,除了我想指定一个数组负责哪些位置。

例如,如果我有一个长度为 80 和 8 个线程的数组,我想确保线程 0 只写入位置 0-9,线程 1 到 10-19 等等。

我对 OpenMP 很陌生,所以我尝试了以下方法:

#include <omp.h>
#include <stdio.h>
#define N       80

int main (int argc, char *argv[]) 
{
    int nthreads = 8, tid, i, base, a[N];

    #pragma omp parallel
    {
        tid = omp_get_thread_num();
        base = ((float)tid/(float)nthreads) * N;
        for (i = 0; i < N/nthreads; i++) {
            a[base + i] = 0;
            printf("%d %d\n", tid, base+i);
        }
    }
    return 0;
}

然而,这个程序并没有像我预期的那样访问所有职位。每次运行时输出都不同,例如:

4 40
5 51
5 52
5 53
5 54
5 55
5 56
5 57
5 58
5 59
5 50
4 40
6 60
6 60
3 30
0 0
1 10

我想我错过了一个指令,但我不知道它是哪一个。

4

3 回答 3

1

你错过了很多。该指令

#pragma omp parallel

仅告诉运行时以下代码块将并行执行,基本上由所有线程执行。但它并没有指定工作要跨线程共享,只是所有线程都要执行该块。要共享工作,您的代码将需要另一个指令,例如这样

#pragma omp parallel
{
    #pragma omp for 
    ...

它是for跨线程分配工作的指令。

但是,您在程序设计中犯了一个错误,这比您不熟悉 OpenMP 的语法还要严重。正如您所建议的那样,跨线程手动分解工作正是 OpenMP 旨在帮助程序员避免的。通过尝试自己进行分解,您正在针对 OpenMP 进行编程并面临两个风险:

  1. 弄错了;尤其是错误的问题,编译器和运行时无需您付出任何努力或思考就可以解决问题。
  2. 精心制作一个运行速度比串行等效程序慢的并行程序。

如果您想对线程的工作分配进行一些控制,请调查该schedule子句。我建议您以这样的方式开始您的并行区域(请注意,我将两个指令融合到一个语句中):

#pragma omp parallel for default(none) shared(a,base,N)
{
    for (i = 0; i < N; i++) {
        a[base + i] = 0;
}

另请注意,我已经指定了变量的可访问性。这是一个很好的做法,尤其是在学习 OpenMP 时。编译器将i自动设为私有。

正如我所写的,运行时会将迭代i分成多个块,每个线程一个。第一个线程将得到i = 0..N/num_threads,第二个i = (N/num_threads)+1..2N/num_threads等等。

稍后您可以将schedule子句显式添加到指令中。我上面写的相当于

#pragma omp parallel for default(none) shared(a,N) schedule(static)

但你也可以尝试

#pragma omp parallel for default(none) shared(a,N) schedule(dynamic,chunk_size)

以及许多其他选项,这些选项在通常的地方都有很好的记录。

于 2013-11-13T13:05:48.103 回答
1

确保事情按照您想要的方式工作的方法是将只有 8 次迭代的循环作为外部(并行)循环,并让每个线程执行一个内部循环,该循环只访问正确的元素:

#pragma omp parallel for private(j)
   for(i = 0; i < 8; i++) {
     for(j = 0; j < 10; j++) {
       a[10*i+j] = 0;
       printf("thread %d updated element %d\n", omp_get_thread_num(), 8*i+j);
     }
   }

我现在无法对此进行测试,但我 90% 确定这完全符合您的要求(并且当您这样做时,您可以“完全控制”事情的工作方式)。然而,这可能不是最有效的做法。一方面 - 当你只想将一堆元素设置为零时,你想使用内置函数memset,而不是循环......

于 2013-11-13T13:48:04.483 回答
0

#pragma omp parallelfor循环并行化是不够的。

嗯...我注意到您实际上尝试手动分配工作。它不起作用的原因很可能是因为计算for循环参数时的竞争条件。

如果我没记错的话,在并行区域之外声明的任何变量都会在线程之间共享。所以所有线程都立即写入i, 。您可以使用适当的/子句使其工作。tidbaseprivateshared

但是,更好的方法是让 OpenMP 分发工作。这就足够了:

#pragma omp parallel private(tid)
{
  tid = omp_get_thread_num();
  #pramga omp for
  for (i = 0; i < N; i++) {
    a[i] = 0;
    printf("%d %d\n", tid, i);
  }
}

请注意,private(tid)它会为每个线程制作一个本地副本tid,因此它们不会在omp_get_thread_num(). 也可以声明shared(a),因为我们希望每个线程都在同一个表副本上工作。现在这是隐含的。我相信迭代器应该被声明为私有的,但我认为pragma当它在并行区域之外声明时,它会处理它,而不是 100% 地处理这种特定情况。但我敢肯定,您实际上可以shared手动设置它并将其搞砸。

编辑:我注意到原来的潜在问题,所以我取出了不相关的部分。

于 2013-11-13T12:55:31.297 回答