Fortran 对不同形状的数组使用单个伪参数

Fortran 对不同形状的数组使用单个伪参数,fortran,Fortran,假设我们有几个不同的连续数组 real :: a1(2),a2(2),a3(2) real :: b1(2,2), b2(2,2),b3(2,2) real :: c1(3,3,3), c2(3,3,3),c3(3,3,3) 和一个子程序 subroutine mtpy(x,y,z) real,contiguous, intent(in) :: x(:), y(:) real, intent(out) :: z(size(x)) z=x*y end subroutine mtpy

假设我们有几个不同的连续数组

real :: a1(2),a2(2),a3(2)
real :: b1(2,2), b2(2,2),b3(2,2)
real :: c1(3,3,3), c2(3,3,3),c3(3,3,3)
和一个子程序

subroutine mtpy(x,y,z)
  real,contiguous, intent(in) :: x(:), y(:)
  real, intent(out) :: z(size(x))

  z=x*y
end subroutine mtpy
如何在以下一系列调用中使用mtpy:

call mtpy(a1,a2,a3)
call mtpy(b1,b2,b3)
call mtpy(c1,c2,c3)
显然,这将导致编译器错误,因为实际参数和伪参数的形状不匹配。在这种情况下,我常常声明几个特定的过程,每个过程处理一个特定的形状,然后使用一个接口包装所有这些过程。然而,这是相当乏味的(想象一下,有大量简单的基本函数和纯过程将多维数组(最多三维)视为一维数组,然后为每个数组提供sub_1d、sub_2d、sub_3d……等实例,尽管所有数组实际上都在做相同的工作)

我想,一个局部解决方案是使用重塑

call mtpy(reshape(b1,[4]),reshape(b2,[4]),bb)
但是,我是否可以确定编译器(我主要对gfortran和ifort感兴趣)不会开始创建1d临时表来保存经过整形的b1和b2数组

现在,我还知道可以声明数组指针,例如

real, pointer, contiguous :: p1(:),p2(:),p3(:)
并进行以下指针赋值,例如

p1(1:size(c1))=>c1
但是,这种方法有一个缺点,即我需要将原始数组声明为目标。这不会影响编译器能够执行的优化吗

我认为,另一种解决方案是使用假定大小的数组,但我注意到Metcalf等人称其用法为“不推荐”,并且我也不确定其对优化的影响


那么,有没有一种简单的方法可以将多维fortran数组视为一维数组(在子例程或函数中),而不会对该数组施加不必要的假设(如目标)?如果我可以使用重塑而不必担心创建临时数组(我只处理连续数组),我会这样做。有什么建议吗?

因为我也不确定
reformate()
是否会生成一个临时数组,即使是在连续的情况下,我也尝试通过
c\u loc()
打印原始数组和传递数组的地址。然后,即使对于小的一维数组,gfortran-8和ifort-16中的
reformate()
似乎也会创建临时数组(因为第一个元素的地址不同)。因此,似乎更安全的假设是,即使在简单的情况下也会创建临时表(有关更多信息,请参阅下面francescalus的评论)

Linux上ifort-16的结果:

 addr(a) =                7040528
 addr(b) =                7040544
 addr(c) =                7040560
 [ use assumed-shape dummy ]
 addr =                7040528
 vals =    1.000000       2.000000    
 --- with reshape() ---
 addr =        140736361693536
 vals =    2.000000       4.000000       6.000000       8.000000    
 addr =        140736361693560
 vals =    3.000000       6.000000       9.000000       12.00000       15.00000       18.00000       21.00000       24.00000  
 --- with linear() ---
 addr =                7040544
 vals =    2.000000       4.000000       6.000000       8.000000    
 addr =                7040560
 vals =    3.000000       6.000000       9.000000       12.00000       15.00000       18.00000       21.00000       24.00000    

 [ use explicit-shape dummy ]
 addr =                7040528
 vals =    1.000000       2.000000    
 addr =                7040544
 vals =    2.000000       4.000000       6.000000       8.000000    
 addr =                7040560
 vals =    3.000000       6.000000       9.000000       12.00000       15.00000       18.00000       21.00000       24.00000    
OSX10.11上gfortran-8的结果:

 addr(a) =       140734555734776
 addr(b) =       140734555734752
 addr(c) =       140734555734720
 [ use assumed-shape dummy ]
 addr =       140734555734776
 vals =    1.00000000       2.00000000    
 --- with reshape() ---
 addr =       140734555734672
 vals =    2.00000000       4.00000000       6.00000000       8.00000000    
 addr =       140734555733984
 vals =    3.00000000       6.00000000       9.00000000       12.0000000       15.0000000       18.0000000       21.0000000       24.0000000    
 --- with linear() ---
 addr =       140734555734752
 vals =    2.00000000       4.00000000       6.00000000       8.00000000    
 addr =       140734555734720
 vals =    3.00000000       6.00000000       9.00000000       12.0000000       15.0000000       18.0000000       21.0000000       24.0000000    

 [ use explicit-shape dummy ]
 addr =       140734555734776
 vals =    1.00000000       2.00000000    
 addr =       140734555734752
 vals =    2.00000000       4.00000000       6.00000000       8.00000000    
 addr =       140734555734720
 vals =    3.00000000       6.00000000       9.00000000       12.0000000       15.0000000       18.0000000       21.0000000       24.0000000

我还认为,根据具体情况,显式形状虚拟数组是有用的,问题中的代码似乎正是这种情况。(因为实际参数是连续的,所以没有创建临时数组。)如果不需要
calc_ver2()
中的size参数
n
,我们可以使用一个返回一维数组指针的函数(请参见上面的
linear()
),但考虑到
calc_ver2()
的简单性,我想这可能有些过分了。。。(顺便说一句,我在代码中的不同位置附加了
target
,这仅仅是因为
c_loc()
需要它)。

未来的Fortran 2018标准将提供假定的秩数组(允许接收任何秩的数组)以及一个
选择秩
结构,该结构将允许使用一个假定秩数组(参见,例如,从第16页开始)轻松解决类似情况,而使用多个假定秩数组则更为困难

假定大小的数组虽然不流行或不推荐,但它们是当前标准(Fortran 2008)以及下一个标准草案(Fortran 2018)的有效、不过时的特性,因此可以在需要时使用。因为很多Fortran 77代码都依赖于此,而且大部分代码已经有几十年的历史了,所以我希望它在大多数编译器中都能得到极大的优化

但是,您不需要使用假定大小的数组,您可以使用显式形状数组(具有显式尺寸的数组),只要数组实际参数有足够的元素,代码就有效,因为根据2008标准第12.5.2.11节第4段

如果伪参数是显式形状或假定大小数组,则表示元素序列并对应于作为数组的伪参数的实际参数是与伪参数关联的序列。实际变元的秩和形状不必与伪变元的秩和形状一致,但伪变元中的元素数量不得超过实际变元元素序列中的元素数量。如果假定伪参数为size,则伪参数中的元素数正好是元素序列中的元素数

所以你可以

call mtpy(a1,a2,a3,size(a3))
call mtpy(b1,b2,b3,size(b3))
call mtpy(c1,c2,c3,size(c3))
...
subroutine mtpy(x,y,z,n)
  integer, intent(in) :: n
  real, intent(in) :: x(n), y(n)
  real, intent(out) :: z(n)
  z=x*y
end subroutine mtpy

如果将秩1数组视为秩2数组,那么是否可以将它们视为秩0并创建基本子例程?如果您必须清楚地对待它们,那么对形状不重要但阵列性质重要的原因进行一些扩展将非常有用。[我不会担心假定的大小,显式的形状被“弃用”:仍然有有效的用途,弃用只是一个视图。]@francescalus感谢您的更正!至于为什么我不能简单地使用元素,嗯,有时候输出不仅依赖于输入,还依赖于一些外部数组之类的数据。理论上,我可以重写代码,只使用基本函数,但实际上这是不可行的。可能还有其他障碍。
重塑
创造了一些东西也就不足为奇了。它是一个返回数据实体的函数。编译器需要做大量的工作来确定所创建的东西是否真的可以被省略(注意停顿问题)。我也这么认为,但我找不到任何网页清楚地说明“重塑()总是使一个临时的”(尽管有些网页建议创建临时的,例如)。我的理解是,它创建了一个临时的,所以避免使用
call mtpy(a1,a2,a3,size(a3))
call mtpy(b1,b2,b3,size(b3))
call mtpy(c1,c2,c3,size(c3))
...
subroutine mtpy(x,y,z,n)
  integer, intent(in) :: n
  real, intent(in) :: x(n), y(n)
  real, intent(out) :: z(n)
  z=x*y
end subroutine mtpy