提问人:Alessandro 提问时间:1/9/2023 最后编辑:Alessandro 更新时间:1/12/2023 访问量:417
在 Fortran 中将数组传递给子例程:假定形状与显式形状
Passing arrays to subroutines in Fortran: Assumed shape vs explicit shape
问:
当将数组传递给过程时,在 (1) 速度和 (2) 内存、假定形状或显式形状方面,什么最好?前段时间在这个论坛上也问过一个类似的问题,但不是用这些术语:在 Fortran 过程中将大小作为参数传递 VS 假设形状
我提供了一个简单的程序来说明我的意思
! Compile with
! ifort /O3 main.f90 -o run_win.exe
module mymod
USE iso_Fortran_env, ONLY: dp => real64
implicit none
private
public :: dp, sub_trace, sub_trace_es
contains
subroutine sub_trace(mat,trace)
! Assumed shape
implicit none
real(dp), intent(in) :: mat(:,:)
real(dp), intent(out) :: trace
real(dp) :: V(size(mat,dim=1))
integer :: i,N
if (size(mat,dim=1) /= size(mat,dim=2)) then
error stop "Input matrix is not square!"
endif
N = size(mat,dim=1)
do i=1,N
V(i) = mat(i,i)
enddo
trace = sum(V)
end subroutine sub_trace
subroutine sub_trace_es(n,mat,trace)
! Passing array explicit shape
implicit none
integer, intent(in) :: n
real(dp), intent(in) :: mat(n,n)
real(dp), intent(out) :: trace
real(dp) :: V(n)
integer :: i
do i=1,n
V(i) = mat(i,i)
enddo
trace = sum(V)
end subroutine sub_trace_es
end module mymod
program main
use mymod, only: dp, sub_trace,sub_trace_es
implicit none
integer, parameter :: nn=2
real(dp) :: mat(nn,nn)
real(dp), allocatable :: mat4(:,:)
real(dp) :: trace1,trace2,trace3,trace4
write(*,*) "Passing arrays to subroutines:"
write(*,*) "Assumed-shape vs explicit shape"
mat(1,:) = [2_dp,3_dp]
mat(2,:) = [4_dp,5_dp]
call sub_trace(mat,trace1)
write(*,*) "trace1 = ", trace1
call sub_trace_es(nn,mat,trace2)
write(*,*) "trace2 = ", trace2
! First example offered by francescalus:
call sub_trace_es(2,real([1,2,3,4,5,6,7,8,9],dp), trace3)
write(*,*) "trace3 = ", trace3
! Second example
mat4 = reshape(real([1,2,3,4,5,6,7,8,9],dp),[3,3])
call sub_trace(mat4, trace4)
write(*,*) "trace4 = ", trace4
pause
end program
答:
使用假定的形状,您可以实现传递非连续数组或无需临时副本的数组。接收子程序知道各个部分在内存中的位置,并且由于数组描述符中的兴奋剂向量,可以在它们之间跳转。这意味着您可以避免临时副本,但通过数组的迭代更复杂,并且可能更慢。
如果假定的形状数组具有该属性,则编译器可以生成更简单、更快的代码,但如果实际参数不连续,则必须创建临时副本。contiguous
对于显式大小的数组,虚拟参数始终是连续的。但是,如果实际参数不连续,则需要临时副本。
使用假定的形状数组,编译器可以在编译过程中更好地检查参数,因为显式接口始终可用。如果显式接口可用,即使对于显式大小的数组,也可以进行一些检查,有时甚至在显式接口不可用时,但可能性更加有限。
其中一个原因是,由于存储关联规则,可以传递一个具有不同秩且总大小(元素数)大于(或等于)大小的数组,该数组以显式大小数组的虚拟参数的形状声明。
对于假定的形状,该形状会与数组描述符一起自动传递。因此,传递小于或大于声明的尺寸对他们来说是一个不存在的概念,它们只是工作方式不同。
在许多方面,这些类型的数组虚拟参数差异太大,并且您可以使用它们执行的操作不同。由于速度,它不仅仅是一个或另一个。它们的使用方式大不相同。对于显式大小数组,您必须以某种方式提供大小。
这种差异可以通过弗朗西斯卡鲁斯提供的例子来说明:
call sub_trace_es(2, real([1,2,3,4,5,6,7,8,9], dp), trace)
这需要跟踪 2x2 数组。传递的参数是一个包含 9 个元素的一维数组。但是,只会考虑前四个。子例程将查看的矩阵是
1 3
2 4
(column-major order),跟踪将为 5。
为
call subtrace( reshape(real([1,2,3,4,5,6,7,8,9], dp), [3,3]), trace)
相同的 9 元素数字序列被重塑为 3x3 数组。因此,子例程将查看的矩阵是
1 4 7
2 5 8
3 6 9
跟踪将为 15。
我个人在超级计算机生产代码的几个地方使用带有显式属性的假定形状,其中传递了大型数组。但是,请注意启用有关临时副本的警告,很容易在一个位置忘记这一点,并且您会通过不必要的临时副本破坏所有内容。contiguous
在我的代码的大多数部分,这些部分对性能不是那么重要,我只是使用假定的形状,而没有其他属性。
评论
call sub_trace_es(2, [1.,2.,3.,4.,5.,6.,7.,8.,9.], trace)
call subtrace(reshape([1.,2.,3.,4.,5.,6. 7.,8.,9.],[3,3]),trace)
real(dp)
real(dp)
1._dp
real(x, dp)
real(8)
评论
contiguous
pause
print *,
real(8)
8