为什么Fortran的内在函数是;散布;通常比显式迭代慢

为什么Fortran的内在函数是;散布;通常比显式迭代慢,fortran,gfortran,fortran90,Fortran,Gfortran,Fortran90,我使用地球物理模型,通常情况下需要将二维数据与三维数据相乘、相加等。下面是一个例子 module benchmarks implicit none integer, parameter :: n=500 integer :: k real :: d2(n,n) real :: d3(n,n,n) contains ! Iteration subroutine benchmark_a(res) real, intent(out) :: res(n,n,n)

我使用地球物理模型,通常情况下需要将二维数据与三维数据相乘、相加等。下面是一个例子

module benchmarks
  implicit none
  integer, parameter :: n=500
  integer :: k
  real :: d2(n,n)
  real :: d3(n,n,n)
  contains
  ! Iteration
  subroutine benchmark_a(res)
    real, intent(out) :: res(n,n,n)
    do k = 1, size(d3,3)
      res(:,:,k) = d2*d3(:,:,k)
    end do
  end subroutine
  ! Spread
  subroutine benchmark_b(res)
    real, intent(out) :: res(n,n,n)
    res = d3*spread(d2, 3, size(d3,3))
  end subroutine
end module

program main
  use benchmarks
  real :: t, tarray(2)
  real :: res(n,n,n)
  call random_number(d2)
  call random_number(d3)
  ! Iteration
  call dtime(tarray, t)
  call benchmark_a(res)
  call dtime(tarray, t)
  write(*,*) 'Iteration', t
  ! Spread
  call dtime(tarray, t)
  call benchmark_b(res)
  call dtime(tarray, t)
  write(*,*) 'Spread', t
end program
当我用不同的维度大小运行这个
n
,我通常会发现
spread
要慢得多;例如:

Spread   2.09942889
Iteration  0.458283991
有人知道为什么
扩展
方法比显式for循环(我认为通常可以不惜一切代价避免)慢得多吗?

这里的基本答案是“不是”。也许在特定的编译器和特定的环境下,内在的优化不如显式的DO循环,但它不必如此。我使用ifort 19进行了测试,即使在默认优化级别下,扩展内部循环和显式循环也会生成类似的代码,当我更正程序以使用结果时,内部循环会更快

迭代0.2187500.1376885
排列9.37050000E-02 0.1376885

我还要提醒大家(正如我在对你的问题的评论中所做的那样),过于简单的基准测试程序通常无法衡量作者认为它们能做什么。最常见的错误是,您的原始示例和修改后的示例都会显示,测试工作的结果从未使用过,因此足够聪明的编译器可以简单地蒸发整个操作。事实上,当我使用ifort19构建两个测试用例时,编译器会完全删除所有工作,只留下计时代码。不用说,它运行得相当快

  implicit none
  integer, parameter :: n=500
  integer :: k
  real :: d2(n,n)
  real :: d3(n,n,n)
  contains
  ! Iteration
  subroutine benchmark_a(res)
    real, intent(out) :: res(n,n,n)
    do k = 1, size(d3,3)
      res(:,:,k) = d2*d3(:,:,k)
    end do
  end subroutine
  ! Spread
  subroutine benchmark_b(res)
    real, intent(out) :: res(n,n,n)
    res = d3*spread(d2, 3, size(d3,3))
  end subroutine
end module

program main
  use benchmarks
  real :: tstart,tend
  real :: res(n,n,n)
  call random_number(d2)
  call random_number(d3)
  ! Iteration
  call cpu_time(tstart)
  call benchmark_a(res)
  call cpu_time(tend)
  write(*,*) 'Iteration', tend-tstart, res(10,10,10)
  ! Spread
  call cpu_time(tstart)
  call benchmark_b(res)
  call cpu_time(tend)
  write(*,*) 'Spread', tend-tstart, res(10,10,10)
end program```

你怎么知道你在安排手术时间?您的示例不使用res,因此编译器可能会蒸发循环。对于do循环,它可能会发现这样做比使用SPREAD更容易。事实上,当我使用ifort尝试您的代码时,它完全删除了扩展和DO循环。那么我如何改进基准测试呢?可以打印
res
,或者向其添加零,或者执行类似的虚拟操作?顺便说一句,我只是用
gfortran
编译。请检查我的编辑。我将这两个示例放在单独的子例程中,每个子例程都返回结果。时间不会改变。当谈到性能时,您应该始终指出问题中的编译器。当处理内在函数时,它更为重要。我添加了标记,但您还应该指出编译器版本(在问题中,而不是注释中)。Fortran没有任何性能,总是单个编译器的性能。您修改的示例仍然没有实际使用res。如果子例程仍然在同一源文件中,则将其放在单独的模块中不会阻止优化。我稍微修改了源代码,以打印主程序中res元素之一的值,使用ifort编译,两个版本的结果都为零。对程序集的检查告诉我,ifort以非常接近DO循环的方式进行了内联扩展。所以实际上你的问题似乎是为什么gfortran不以循环的方式优化传播-与传播本身无关。谢谢!我应该意识到性能取决于编译器;实际上,我们才刚刚开始使用低级语言。你能再试一次添加测试吗?好吧,你只需要用三个背号把文本围起来。我试着只打印
res
,但代码仍然被
pgf90
蒸发掉。还有,在您的示例中,第二个数字是什么?实际上,在第二次调用
dtime
之前,即使在
open(1,file='data.dat')
write(1,*)res
写入文件时,我仍然会得到“零”使用
pgf90
经过的时间,即使我实时等待几秒钟,等待程序完成运行。也许这可能是一个特定于pgi的问题?我用dtime替换了CPU时间,得到了合理的数字。我还得到了零dtime值。dtime是非标准的,在不同的实现中,其行为可能会有所不同。我知道怎么格式化,但不需要花时间。第二个数字就是res(10,10,10)的值-强制编译器执行操作的值。啊,现在编辑好了。太好了,谢谢!很高兴知道
传播速度通常更快。。。但我想这在很大程度上取决于大小和可用内存;问题是,创建临时平铺数组(即“广播”它)然后执行单个矢量化二进制操作是否更快,或者避免创建临时数组但必须运行一系列二进制操作。也许某处有一个交叉点。将运行更多测试。