我目前正在使用C和OpenMP学习并行编程。
我想编写简单的代码,其中两个共享值由多个线程递增。
首先,我使用了 reduction 指令,它的工作原理就是这样。然后我转而使用批判性指令来启动关键部分 - 它也有效。
出于好奇,我试图合并这两个解决方案并检查行为。我期待两个有效的,相等的值。
代码:
#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
critical = 4246432
当然,当涉及到关键的&#39;时,输出是随机的。 (变量y),另一个表现符合预期,总是50000.
x 的行为是可以理解的 - 缩减会使其在单线程范围内变为私有。在将线程的增量值相加并传递给非局部x之后。
我不明白的是 y 的行为。它是私有的,就像 x 一样,但它也在批评部分内,所以它有多个原因&#39;从其他线程无法访问。然而,我认为,恰好是竞争条件。 批判性是否以某种方式使 y 公开(共享)?
我知道这段代码没有任何意义,因为它只能使用减少 / 关键中的一个。我只想知道这种行为背后的原因。
答案 0 :(得分:6)
您的代码只显示未定义的行为,critical
的存在与您获得错误结果无关。
严重是否以某种方式使 y 公开(共享)?
不,它没有。它只会通过阻止并发执行线程来减慢循环。
您缺少的是缩减操作的结果与缩小变量的初始值相结合,即与变量在并行区域之前具有的值相结合。在您的情况下,x
和y
都有随机初始值,因此您会得到随机结果。在您的情况下,初始值x
恰好为0,这就是为什么您获得正确结果的原因就是UB。初始化x
和y
会使您的代码按预期运行。
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
答案 1 :(得分:5)
您的代码的主要问题是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;
}
icc -O3 -Wall -qopenmp -std=c99 redcrit.c
OpenMP threads = 4
reduce = 50000
critical = 50000
atomic = 50000