【问题标题】:OpenMP - critical section + reductionOpenMP - 临界区 + 缩减
【发布时间】:2016-05-12 14:03:09
【问题描述】:

我目前正在学习使用 C 和 OpenMP 进行并行编程。 我想编写简单的代码,其中两个共享值由多个线程递增。 首先,我使用了 reduction 指令,它按预期工作。然后我切换到使用 critical 指令来启动临界区——它也有效。 出于好奇,我尝试合并这两个解决方案并检查行为。我希望有两个有效、相等的值。

代码:

#include <stdio.h>
#include <stdlib.h>
#include "omp.h"

#define ITER 50000

int main( void )
{
    int x, y;
    #pragma omp parallel reduction(+:x,y)
    {
       #pragma omp for
       for (int i = 0; i < ITER; i++ )  
       {
            x++;
            #pragma omp critical
            y++;
       }
    }

    printf("non critical = %d\ncritical = %d\n", x, y);
    return 0;
}

输出:

非关键 = 50000
关键 = 4246432

当然,当涉及到“关键”(变量 y)时,输出是随机的,另一个则按预期运行,始终为 50000。

x 的行为是可以理解的 - reduction 使其在单线程范围内是私有的。在对来自线程的增量值求和并传递给非本地 x 之后。

我不明白的是y的行为。它就像 x 一样是私有的,但它也在 critical 部分内,因此它“有多个理由”无法从其他线程访问。然而,我认为,发生的是比赛条件。 critical 是否以某种方式将 y 公开(共享)?

我知道这段代码没有意义,因为只使用 reduction / critical 之一就足够了。我只想知道这种行为背后的原因。

【问题讨论】:

  • 谁初始化变量?好像是 UB。
  • OpenMP 在 reduction 指令中自动执行此操作(0 表示 + 和 -,1 表示 * 和 /)
  • 用于私有副本,但不适用于可见变量。如果你初始化它会发生什么?
  • 我确信减少隐含地私有化了减少的值。
  • @nullPointer 您在技术上是正确的。但是,当您可以冗长并完全说服自己您正在编写的内容完全按照您的预期工作时,为什么还要依赖 OpenMP 隐式行为呢?

标签: c multithreading openmp critical-section reduction


【解决方案1】:

您的代码只是表现出未定义的行为,critical 的存在与您得到错误结果无关。

critical 是否以某种方式将 y 公开(共享)?

不,它没有。它只会通过阻止线程的并发执行来减慢循环。

您缺少的是归约操作的结果与归约变量的初始值相结合,即与变量在平行区域之前的值相结合。在您的情况下,xy 都具有随机初始值,因此您会得到随机结果。在您的情况下,初始值 x 恰好为 0,这就是为什么您得到正确结果的原因只是 UB。初始化 xy 会使您的代码按预期运行。

OpenMP 规范规定:

reduction 子句指定一个reduction-identifier 和一个或多个列表项。对于每个列表项,在每个隐式任务或 SIMD 通道中创建一个私有副本,并使用 reduction-identifier 的初始化器值进行初始化。在区域结束后,原始列表项使用与 reduction-identifier 关联的组合器使用私有副本的值更新 .

这是您使用 4 个线程执行的原始代码:

$ icc -O3 -openmp -std=c99 -o cnc cnc.c
$ OMP_NUM_THREADS=1 ./cnc
non critical = 82765
critical = 50000
$ OMP_NUM_THREADS=4 ./cnc
non critical = 82765
critical = 50000
$ OMP_NUM_THREADS=4 ./cnc
non critical = 50000
critical = 50000
$ OMP_NUM_THREADS=4 ./cnc
non critical = 82765
critical = 50194
$ OMP_NUM_THREADS=4 ./cnc
non critical = 82767
critical = 2112072800

使用一个线程的第一次运行表明它不是由于数据竞争。

int x=0, y=0;:

$ icc -O3 -openmp -std=c99 -o cnc cnc.c
$ OMP_NUM_THREADS=4 ./cnc
non critical = 50000
critical = 50000
$ OMP_NUM_THREADS=4 ./cnc
non critical = 50000
critical = 50000
$ OMP_NUM_THREADS=4 ./cnc
non critical = 50000
critical = 50000
$ OMP_NUM_THREADS=4 ./cnc
non critical = 50000
critical = 50000

【讨论】:

    【解决方案2】:

    您的代码的主要问题是 xy 未初始化。第二个问题是临界区中使用的变量应该是shared 而不是归约变量,尽管这只会影响性能,而不是正确性。

    我已经更正了您的代码并对其进行了修改,以演示 reducecriticalatomic 如何产生相同的结果。

    来源

    #include <stdio.h>
    #include <stdlib.h>
    #include <omp.h>
    
    int main(int argc, char* argv[])
    {
        int iter = (argc>1) ? atoi(argv[1]) : 50000;
        int r=0, c=0, a=0;
    
        printf("OpenMP threads = %d\n", omp_get_max_threads() );
    
        #pragma omp parallel reduction(+:r) shared(c,a)
        {
            #pragma omp for
            for (int i = 0; i < iter; i++ ) {
                r++;
                #pragma omp critical
                c++;
                #pragma omp atomic
                a++;
            }
        }
        printf("reduce   = %d\n"
               "critical = %d\n"
               "atomic   = %d\n", r, c, a);
        return 0;
    }
    

    编译

    icc -O3 -Wall -qopenmp -std=c99 redcrit.c
    

    输出

    OpenMP threads = 4
    reduce   = 50000
    critical = 50000
    atomic   = 50000
    

    【讨论】:

    • 感谢您的精彩回答!双重计数解释了很多。但是,我并不完全确定您提到的“未定义行为”。我在大学有这门课程,我们的导师从未初始化过缩减值 - 我被告知它们是由 OpenMP 以 0 或 1 启动的,具体取决于缩减的运算符。 Tbh 我也做过同样的事情,除了上述情况之外,它总是有效的。编辑:虽然,我必须说,当我初始化值 x,y 而不更改任何其他内容时,代码就像@2501 建议的那样工作
    • 是的,我看到 OpenMP 定义了归约变量的默认初始值设定项。但是,ISO C 没有局部变量的默认初始化程序 (stackoverflow.com/a/1597426/2189128),这意味着如果未启用 OpenMP,您的程序将具有未定义的行为。你不想要那个。此外,critical 变量没有默认初始化程序。
    • 我相信你的最后一句话对我的问题也很关键。再次感谢您帮助我理解它:)
    • @nullPointer 确实,我没有明确指出这一点,但是是的,这可以解释你的结果。感谢您的关注。
    • 你的第一句话对我来说毫无意义。 critical 的存在除了减慢代码速度外,绝不会改变归约属性。代码只是 UB,因为 xy 没有初始化。在parallel 区域之前将它们设置为0 会产生正确的输出。归约变量的 private 副本被初始化为零,但是归约的结果被添加到变量在区域之前的值中,因此是 UB。
    猜你喜欢
    • 2014-12-08
    • 1970-01-01
    • 2015-12-26
    • 1970-01-01
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多