【问题标题】:Fortran OpenMP calculation for partial sums部分和的 Fortran OpenMP 计算
【发布时间】:2019-03-27 22:47:22
【问题描述】:

我对 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

这个程序返回

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

返回

 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

为了计算局部总和。

【问题讨论】:

  • 如果没有localsum(I)=I,在尝试与totals = totals + localsum(I) 求和之前,您没有定义数组。你的意思是你改用totals = totals + I
  • 我认为localsum(I)=I是并行运行程序所必需的。如果我调整totals=totals + I,那将是一个顺序实现。

标签: fortran openmp reduction


【解决方案1】:

这是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

【讨论】:

  • 如果一个新问题是重复的,不要发布两个完全相同的答案,投票关闭作为重复或评论。
  • 是的,我同意,但我还没有足够的 Stackoverflow-fu 来做到这一点。
  • 只是不要在多个问题下发布相同的答案。您可以随时评论该问题是重复的。
【解决方案2】:

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

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

假设线程 a 和线程 b 想要更新这个 var。他们需要:
1. 从内存中获取 var
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

性能将得到改善,结果相同。并且两个循环都以类似的方式并行化。

【讨论】:

  • !$omp+ reduction(+:totals) 并将do 循环更改为totals = totals + I 有时会返回错误的答案。总和有时计算为 4679.00000 而不是 5050.0000。这就是我感到困惑的地方——如果这是正确的,它总是会返回 5050.0000。
  • 虽然,我认为我应该为这个观察提出一个新问题。代码显然必须包含!$omp+ reduction(+:totals)。所以,我一定间接地做错了什么。
  • 我会提出一个新问题供我观察。感谢您非常有帮助的回答。
  • 抱歉,注释不正确,因为 OpenMP 指令的格式在语法上不正确。不过,关于竞争条件的陈述是正确的。
猜你喜欢
  • 1970-01-01
  • 2023-04-01
  • 2014-11-28
  • 2012-06-04
  • 1970-01-01
  • 2013-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多