OpenMP - 临界区 + 缩减
OpenMP - critical section + reduction
我目前正在学习使用 C 和 OpenMP 进行并行编程。
我想编写简单的代码,其中两个共享值由多个线程递增。
首先,我使用了 reduction 指令,它按预期工作。然后我切换到使用 critical 指令来启动临界区 - 它也有效。
出于好奇,我试图合并这两个解决方案并检查行为。我期望两个有效的、相等的值。
code:
#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;
}
输出:
non critical = 50000
critical = 4246432
当然输出是随机的,当涉及到'critical'(变量y)时,另一个表现符合预期并且总是50000。
x 的行为是可以理解的 - reduction 使其在单线程范围内是私有的。在将来自线程的增量值相加并传递给非本地 x 之后。
我不明白的是y的行为。它和 x 一样是私有的,但它也在 critical 部分中,因此它 'has more than one reason' 无法从其他线程访问。然而,我认为,发生的是竞争条件。 critical 是否以某种方式使 y public(共享)?
我知道这段代码没有意义,因为只使用 reduction / critical 之一就足够了。我只想知道这种行为背后的原因。
您的代码的主要问题是 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
您的代码只是表现出未定义的行为,critical
的存在与您得到错误的结果无关。
Did the critical somehow made y public (shared)?
不,它没有。它只会通过阻止线程的并发执行来减慢循环。
您缺少的是缩减操作的结果与缩减变量的初始值相结合,即与变量在并行区域之前的值相结合。在您的情况下, x
和 y
都具有随机初始值,因此您将获得随机结果。在您的情况下,初始值 x
恰好为 0,这就是您获得正确结果的原因,因为它只是 UB。初始化 x
和 y
会使您的代码按预期运行。
OpenMP 规范指出:
The reduction
clause specifies a reduction-identifier and one or more list items. For each list item, a private copy is created in each implicit task or SIMD lane, and is initialized with the initializer value of the reduction-identifier. After the end of the region, the original list item is updated with the values of the private copies using the combiner associated with the 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
我目前正在学习使用 C 和 OpenMP 进行并行编程。
我想编写简单的代码,其中两个共享值由多个线程递增。
首先,我使用了 reduction 指令,它按预期工作。然后我切换到使用 critical 指令来启动临界区 - 它也有效。
出于好奇,我试图合并这两个解决方案并检查行为。我期望两个有效的、相等的值。
code:
#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;
}
输出:
non critical = 50000
critical = 4246432
当然输出是随机的,当涉及到'critical'(变量y)时,另一个表现符合预期并且总是50000。
x 的行为是可以理解的 - reduction 使其在单线程范围内是私有的。在将来自线程的增量值相加并传递给非本地 x 之后。
我不明白的是y的行为。它和 x 一样是私有的,但它也在 critical 部分中,因此它 'has more than one reason' 无法从其他线程访问。然而,我认为,发生的是竞争条件。 critical 是否以某种方式使 y public(共享)?
我知道这段代码没有意义,因为只使用 reduction / critical 之一就足够了。我只想知道这种行为背后的原因。
您的代码的主要问题是 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
您的代码只是表现出未定义的行为,critical
的存在与您得到错误的结果无关。
Did the critical somehow made y public (shared)?
不,它没有。它只会通过阻止线程的并发执行来减慢循环。
您缺少的是缩减操作的结果与缩减变量的初始值相结合,即与变量在并行区域之前的值相结合。在您的情况下, x
和 y
都具有随机初始值,因此您将获得随机结果。在您的情况下,初始值 x
恰好为 0,这就是您获得正确结果的原因,因为它只是 UB。初始化 x
和 y
会使您的代码按预期运行。
OpenMP 规范指出:
The
reduction
clause specifies a reduction-identifier and one or more list items. For each list item, a private copy is created in each implicit task or SIMD lane, and is initialized with the initializer value of the reduction-identifier. After the end of the region, the original list item is updated with the values of the private copies using the combiner associated with the 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