为什么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)的值-强制编译器执行操作的值。啊,现在编辑好了。太好了,谢谢!很高兴知道传播速度通常更快。。。但我想这在很大程度上取决于大小和可用内存;问题是,创建临时平铺数组(即“广播”它)然后执行单个矢量化二进制操作是否更快,或者避免创建临时数组但必须运行一系列二进制操作。也许某处有一个交叉点。将运行更多测试。