3

我理解网络上关于atomicC++ 中 OpenMP 指令的解释的方式是它们适用于特定的内存位置,由某个变量(或其指针?)指定。因此,当我在并行for循环内的不同代码行上访问此位置时,我可以保护所有这些代码还是atomic只保护一行代码而不查看访问同一内存位置的其他可能行?

例如,考虑以下代码:

int N = 10000;  // just some big number
float a[N];     // a big array
#pragma omp parallel for
for(int i = 1; i < N-1; i++) {
    #pragma omp atomic
    a[i-1] += 0.5f;
    #pragma omp atomic
    a[i]   += 1.0f;
    #pragma omp atomic
    a[i+1] += 0.5f;
}

在每次循环迭代中,在三个点处访问同一个数组,索引处ii减一i和加一。但是,在不同的线程中,该i-1行的计算结果可能与 the iori+1行相同。例如,在线程 1i==1和线程 2i==3中,第三个(在线程 1 中)和第一个(在线程 2 中)数组访问行将访问相同的数组元素,可能同时访问。

atomic如果它们碰巧访问相同的内存位置,它们会保护这些不同的行吗?还是它只适用于一行,并且唯一的解决方案是将所有三个访问合并到一行中(例如,通过将i-1,ii+1放在第二个数组中并制作第二个for循环来循环它们)?

4

2 回答 2

4

来自 OpenMP 标准 3.1(第 2.8.5 节):

atomic构造确保以原子方式访问特定存储位置,而不是将其暴露于可能导致不确定值的多个同时读取和写入线程的可能性。

所以,给你一个简短的回答:

atomic如果它们碰巧访问相同的内存位置,它们会保护这些不同的行吗?

是的,它会的。

但让我再详细说明一下。根据标准,构造的语法如下:

#pragma omp atomic new-line 
   expression-stmt

哪里expression-stmt有表格:

x binop= expr
x++
++x
x--
--x

当然你有一些限制:

  1. x必须是标量类型的左值表达式
  2. expr是一个标量类型的表达式,它不引用指定的对象x
  3. binop不是重载运算符,并且是其中之一+, *, -, /, &, ^, |, <<, or >>
  4. 整个程序中对存储位置的所有原子引用x都需要具有兼容的类型

所有这些约束都由您的代码段实现。特别是,第 4 点x无关紧要,代码中的浮点数始终如此(换句话说,a[i]始终返回浮点数)。显示违反第 4 点的通常示例是使用 a union,如其他答案中发布的链接中所示。

于 2013-06-07T17:12:58.510 回答
3

编辑:我在 Visual Studio 2012 中的第一次尝试失败了。但我想不出它不应该工作的任何原因。我将常量更改为浮点而不是双精度(0.5f 而不是 0.5)。现在它起作用了。因此,要回答您的问题,只要您使用相同的数据类型(不要混合浮点和双精度),您就可以像以前那样使用原子。我在这里学到了这个准备http://msdn.microsoft.com/en-us/library/5fhhcxk3.aspx(整个程序中对存储位置 x 的所有原子引用都需要具有兼容的类型。)

void foo_v1(float *a, const int N) {
    #pragma omp parallel for
    for(int i = 1; i < N-1; i++) {
        #pragma omp atomic
        a[i-1] += 0.5f;
        #pragma omp atomic
        a[i] += 1.0f;
        #pragma omp atomic
        a[i+1] += 0.5f;
    }
}

在我意识到您的代码是混合类型之前,以下是我的原始答案。无论如何,这是一个更好的解决方案:-)

不,那是行不通的(见我上面的更正)。您可以通过使用和不使用 OpenMP 生成结果并进行比较来自己检查这些事情,您会发现它失败了(请参阅上面的更正)。你应该做一张桌子看看发生了什么

          a[0]   a[1]   a[2]   a[3]   a[4]   a[5] ....
i=1      +=0.5  +=1.0  +=0.5
i=2             +=0.5  +=1.0  +=0.5
i=3                    +=0.5  +=1.0  +=0.5
i=4                           +=0.5  +=1.0  +=0.5
  .

请注意,对于 a[2] 到 a[N-3],它们只是a[i] = 0.5 + 1.0 + 0.5. 如果需要,您可以将常量 (0.5, 1.0, 0.5) 替换为值数组。使用此代码。

void foo_v2(float *a, const int N) {
    a[0] += 0.5;
    a[1] += 0.5 + 1.0;
    a[N-1] += 0.5;
    a[N-2] += 1.0 + 0.5;

    #pragma omp parallel for
    for(int i = 2; i < N-2; i++) {
        a[i] += 0.5 + 1.0 + 0.5;
    }
}
于 2013-06-07T12:42:07.227 回答