OpenMP - 关键部分+减少

2 c multithreading openmp critical-section reduction

我目前正在学习使用C和OpenMP进行并行编程.我想编写简单的代码,其中两个共享值由多个线程递增.首先,我使用了还原指令,它的工作原理就是这样.然后我切换到使用关键指令启动关键部分 - 它也有效.出于好奇,我试图合并这两个解决方案并检查行为.我期待两个有效的,相等的值.

码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

非关键= 50000
关键= 4246432

当然,当涉及到'critical'(变量y)时,输出是随机的,另一个表现为预期的并且总是50000.

x的行为是可以理解的 - 减少使其在单线程范围内是私有的.在将线程的增量值相加并传递给非局部x之后.

我不明白的是y的行为.它就像x一样私有,但它也在临界区内,所以它有多个原因让其他线程无法访问.然而,我认为,恰好是竞争条件.难道关键不知何故Ÿ公共(共享)?

我知道这段代码没有任何意义,因为它只能使用一个简化/关键.我想知道这种行为背后的原因.

Jef*_*eff 6

您的代码的主要问题是x并且y未初始化.第二个问题是临界区中使用的变量应该是shared减少变量而不是减少变量,尽管这只会影响性能,而不是正确性.

我已经纠正了你的代码并对其进行了修改以演示如何reduce,critical并且atomic所有代码都产生相同的结果.

资源

#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;
}
Run Code Online (Sandbox Code Playgroud)

icc -O3 -Wall -qopenmp -std=c99 redcrit.c
Run Code Online (Sandbox Code Playgroud)

产量

OpenMP threads = 4
reduce   = 50000
critical = 50000
atomic   = 50000
Run Code Online (Sandbox Code Playgroud)

  • 你的第一句话对我没有意义.除了减慢代码速度之外,"critical"的存在决不会改变减少属性.代码只是UB,因为`x`和`y`没有初始化.在"parallel"区域之前将它们设置为"0"会产生正确的输出.减少变量的**私有**副本初始化为零,但随后减少的结果被添加到变量在区域之前的值,因此UB. (2认同)

Hri*_*iev 6

您的代码只显示未定义的行为,并且存在critical与您获得错误结果无关.

难道关键不知何故Ÿ公共(共享)?

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

您缺少的是减少操作的结果与减少变量的初始值组合,即与变量在并行区域之前具有的值相结合.在你的情况,都xy具有随机初始值,所以你得到随机的结果.x在你的情况下,初始值恰好为0,这就是为什么你得到正确的结果就是UB.初始化都xy使你的代码像预期的那样.

OpenMP规范声明:

reduction子句指定redu-identifier和一个或多个列表项.对于每个列表项,在每个隐式任务或SIMD通道中创建私有副本,并使用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
Run Code Online (Sandbox Code Playgroud)

第一次运行一个线程表明它不是由于数据竞争.

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
Run Code Online (Sandbox Code Playgroud)