Fortran `forall` 或 `do concurrent` 中的临时变量

Posted

技术标签:

【中文标题】Fortran `forall` 或 `do concurrent` 中的临时变量【英文标题】:Temporary variables inside Fortran `forall` or `do concurrent` 【发布时间】:2013-09-11 10:41:42 【问题描述】:

我有一个使用forall 制定的数组分配。必须根据循环变量计算一个数组的索引,因此我尝试以类似于ii 这个简单示例的方式使用临时变量:

program test
  integer :: i, ii, a(10), b(20)
  a=(/(i, i=1,10)/)

  forall (i=1:20)
     ii = (i+1)/2

     b(i) = a(ii)
  end forall

  print '(20I3)', b
end program test

gfortran 警告我 “索引为 'i' 的 FORALL 未在 (1) 处的赋值左侧使用,因此可能导致对该对象的多次赋值”,确实输出不是我想要的:

10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10

ifort 甚至会抛出错误。)我意识到我可以重复 ii 每次出现时的定义,但这在现实生活中会变得乏味。

Q1:有没有办法使用forall正确地做这种事情?


现在,正如您在某些other questions 中看到的那样,似乎不再推荐forall,而直接使用do 甚至可能更快。

但是,在这种情况下,我对forall 的使用与可读性和速度一样重要。在我的真实程序中,我有类似forall (i=1:10, j=1:10, i<=j) 的东西,我喜欢forall 标头让我在没有嵌套循环的情况下指定它的方式。

我也在这些问题中了解到do concurrent,这似乎解决了问题,即将上面的forall替换为do concurrent,输出变为:

1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10

我仍然可以使用 forall 标头。

Q2:这种在forall 中不起作用的局部变量的使用是否由标准保证在do concurrent 中起作用? (而不是它在gfortran 中工作的侥幸。)

不幸的是,现在我无论如何都不能使用do concurrent,因为稍旧的ifortgfortran 版本不支持它。

【问题讨论】:

在 DO CONCURRENT 构造中可以使用局部临时变量,因为在 Fortran 中,并发实际上意味着循环迭代的任意顺序,而不是并行性。 【参考方案1】:

对于您问题中的情况,我认为根本不需要标量 ii,您可以替换

  forall (i=1:20)
     ii = (i+1)/2

     b(i) = a(ii)
  end forall

  forall (i=1:20)
     b(i) = a((i+1)/2)
  end forall

在这种情况下,它会产生您想要的结果。至于你的问题:

A1:是的,如图所示。

A2: do concurrent 是 Fortran 2008 中的标准功能,因此保证可以正常工作;保证您是否拥有最新的编译器并且该编译器已正确实现。到目前为止,我只将do concurrent 与最新版本的英特尔 Fortran 编译器一起使用,并且没有发现任何非标准行为。

在弗拉基米尔 F 的评论之后

是的,重写的forall 构造可能需要在幕后使用临时数组,但我相信我写的内容在语法上是正确的,并且在语义上与 OP 的原始代码等效。至于do concurrent 构造的合法性,如下

do concurrent (i=1:20)
   b(i) = a((i+1)/2)
end do

编译并执行得很好。同样,我认为这是符合标准的代码和行为。

【讨论】:

备注:这个forall 解决方案将需要一个临时数组,但这可能不是问题。对于do concurrent,这可能是非法的。但除此之外 +1 @Mark Ad 1:这是我试图避免的。当然,这只是方便和可读性的问题。广告 2:我编辑了问题以更明确地说明这一点。 (我认为你的答案仍然是“是”)。 @VladimirF:为什么该解决方案在do concurrent 中是非法的? @VladimirF 为什么你认为 forall 需要一个临时数组?这并没有让我觉得编译器应该挣扎。提出的 do concurrent 似乎是合法的(请注意,使用 ii 临时变量方法也是合法的,这是 do concurrent 的一个简洁方面)。 您似乎很喜欢在 foralldo concurrent 构造中使用 ii 的想法。我看不出有必要,所以我怀疑你问的是你真正问题的简化形式。我看到 IanH 现在已经正确回答了您的部分问题,而我没有回答。【参考方案2】:

正如 Mark 所指出的,在 forall 构造内定义 ii 是不必要的,因为您可以在定义中使用 a((i+1)/2)。作为另一种选择,如果您完全喜欢使用forall,您可以使用pure function(或elemental function)来设置索引:

program forall_test
   ...
  forall(i=1:20)
     b(i) = a(set(i))
  end forall 

contains
  pure function set(i)
    integer, intent(in) :: i
    integer :: set
    set = (i+1)/2
  end function set
end program

将它与a=(/1, 2, 3, 4, 5, 6, 7, 8, 9, 10/) 一起使用,我得到了输出

1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10

【讨论】:

赞成,最有趣的扩展,使简单的事情变得复杂 :) 当然,在一个更真实的例子中,这可能是一个很好的接触 @steabert:如果有人想有意通过使用“更优雅”的变通方法来限制他们编写好代码的能力,那么就必须让事情变得复杂 :) - -实话实说,如果索引更复杂一些(比如涉及ifs),这可能是一个非常好的方法(当然,假设你仍然将自己限制在forall) . 谢谢,这是我的问题的实际答案:-)【参考方案3】:

您似乎在使用forall,而我自然会使用do 循环:

do i=1,20
  ii=(i+1)/2
  b(i)=a(ii)
end do

forall 语句对于您希望对整个数组赋值应用掩码的条件单行语句特别有用,但即便如此,我个人更喜欢 do 循环。

forall (i=1:10, j=1:10, i<=10) 的示例也没有多大意义,因为最终表达式不会屏蔽任何值。

forall 和其他整个数组赋值背后的想法是编译器会知道如何更好地优化整个事情,但在实践中(嗯,至少自从我上次亲自检查以来),它通常会导致代码变慢.

编辑:

只是想提一下,将计算的整数直接分配给可能会更好 b中的数组值,而不是通过使用a再次从内存中获取相同的东西:

do i=1,20
  b(i)=(i+1)/2
end do

但我希望这仅仅是因为示例过于简化了? :)

【讨论】:

至于面具,我是想写forall (i=1:10, j=1:10, i<=j);我在问题中纠正了这一点。在这个和示例程序中,我试图将我的观点归结为一个简短的示例——如果我过于简单化了,请见谅。至于 do 循环:我知道,但我认为 foreach 更优雅(请参阅原始问题)。 直到今天,我从未想过有人会认为forall 构造比do 循环“更优雅”。 @KyleKanos:嗯,你可以看到我不是真正的 Fortran 程序员 :-)。对我来说,forall 可以在一行中表达两个(或更多)嵌套循环。 @xebtl 我明白这一点,但是在很多情况下,循环体变得更加复杂并且您想要优化它,无论如何您都需要使用 do 循环,它可能看起来甚至比一个简单的嵌套 do 循环 :) 但我明白你的意思,不幸的是,根据经验,我发现性能通常与优雅成反比。 我猜应该是“反向”,而不是“反向”

以上是关于Fortran `forall` 或 `do concurrent` 中的临时变量的主要内容,如果未能解决你的问题,请参考以下文章

Fortran 95 构造(例如 WHERE、FORALL 和 SPREAD)通常会导致更快的并行代码吗?

继续在教堂的 FORALL 循环之外

开放式加速器 | Fortran 90:并行化嵌套 DO 循环的最佳方法是啥?

系统地并行化 fortran 2008 `do concurrent`,可能使用 openmp

整数变量在 forall 构造后不再可用

openmp+fortran程序,双重do循环外面都加并行,结果好像并行了,但是threadid都是0,请问到底并行没有?