部分和的 Fortran OpenMP 计算

Fortran OpenMP calculation for partial sums

我是 Fortran 的新手,正在完成一个涉及通过 OpenMP 并行求和数字的练习。

我得到以下代码通过 OpenMP 并行正确计算数字总和

!$omp parallel do private (I)
!$omp+ reduction(+:totals)
do I=1,100
    totals = totals + localsum(I)
enddo
!$omp end parallel do

如果我调整上面的代码以便我可以 运行 它在我自己的 Fortran 程序中,我生成

Program test
    implicit none
    real totals
    double precision, dimension (1 : 100) :: localsum
    integer I

    !$omp parallel do private (I)
    !$omp+ reduction(+:totals)
    do I=1,100
        localsum(I)=I
        totals = totals + localsum(I)
    enddo
    !$omp end parallel do
    print *, 'The calculated sum total is', totals
end

这个节目returns

The calculated sum total is   5050.00000

但是,我不确定为什么需要为

添加额外的行
localsum(I)=I

当原始代码没有这一行时。我注意到如果我删除

!$omp+ reduction(+:totals)

然后

Program test
    implicit none
    real totals
    double precision, dimension (1 : 100) :: localsum
    integer I

    !$omp parallel do private (I)
    do I=1,100
        localsum(I)=I
        totals = totals + localsum(I)
    enddo
    !$omp end parallel do
    print *, 'The calculated sum total is', totals
end

returns

 The calculated sum total is   5050.00000

当计算的总数应该是错误的。包括减少,!$omp+ reduction(+:totals),应该是计算正确总数所必需的。

是否有其他方法可以调整 do 循环以匹配提供的原始代码?我不确定为什么我必须改变

do I=1,100
    totals = totals + localsum(I)
enddo

do I=1,100
    localsum(I)=I
    totals = totals + localsum(I)
enddo

为了计算本地总和

有或没有 !$omp+ reduction(+:totals) 执行的代码是不同的。

没有这个指令,你直接更新全局变量totals。这可能有效(在您的示例中有效),但远不能保证。问题是这可能会导致竞争。

假设线程a和线程b想要更新这个var。他们需要:
1. 从内存中获取变量
2. 在处理器中更新它
3. 将其写回内存

这些操作在线程 a 和 b 中的相对顺序是什么?未指定。
如果顺序是1a2a3a1b2b3b,没有问题。
如果是1a1b2a2b3a3b就会出现问题:1a1b(线程a和b获取相同的值)2a2b(它们或多或少同时更新)3a3b(线程a写入它的结果,它被线程b的值覆盖)。

为避免这种情况,您可以进行原子操作,以保证读取-修改-写入循环不会被中断,但它们非常昂贵并且可能会显着减慢执行时间。

为避免这种情况,您必须使用缩减。 !$omp+ reduction(+:totals) 行告诉 openmp 以安全有效的方式进行缩减。实际要做的是

  1. 设置一个隐藏的局部变量来在部分循环中进行累加
  2. 在循环的每次迭代中,在此局部变量中执行累加
  3. 最后以一种安全的方式将这些部分结果累积到全局变量 totals:将以正确更新全局变量并避免线程之间竞争的方式执行原子操作。

仍然有原子更新,但它们的数量减少了,并且积累主要由快速本地操作执行。

关于行 localsum(I)=I 的有用性,前提是向量 localsum 之前没有初始化。但是如果目标只是添加第一个整数,你可以只使用

do I=1,100
    totals = totals + I
enddo

性能会有所提升,结果相同。并且两个循环都以类似的方式并行化。

这是 Odd results with !$omp reduction in Fortran OpenMP 的副本。

根据 OpenMP 规范(请参阅第 42 页),OpenMP 指令的正确延续是在前一行的末尾使用 & 并在后续行中使用 !$omp&!$omp& 中的符号是可选的)。所以,所以你的代码应该是这样的:

Program test
    implicit none
    real totals
    integer I
    integer, dimension(100) :: localsum
    !$omp parallel do private (I) &
    !$omp& reduction(+:totals)
        do I=1,100
            localsum(I)=I
            totals = totals + localsum(I)
        enddo
    !$omp end parallel do
    print *, 'The calculated sum total is', totals
end

由于编译器忽略了定义对 totals 变量的缩减的续行,因此您得到了任意结果。添加适当的延续后,我得到了正确的结果:

The calculated sum total is   5050.000