Fortran 90自动(?)分配可分配项

Fortran 90自动(?)分配可分配项,fortran,allocation,fortran90,Fortran,Allocation,Fortran90,在上面的代码中,我设置了两个可分配项-a和b,并且只在代码中分配了a。我原以为代码无法编译,但它编译得很好,而且似乎工作得很好,而下面的代码也显示了SEGFAULT,它做了类似的工作 program test implicit none real(dp),allocatable :: a(:), b(:) allocate (a(10)) a = 1.d0 (*) b = a end program 理解前一个代码自动分配b是否合适 program te

在上面的代码中,我设置了两个可分配项-
a
b
,并且只在代码中分配了
a
。我原以为代码无法编译,但它编译得很好,而且似乎工作得很好,而下面的代码也显示了SEGFAULT,它做了类似的工作

program test

    implicit none
    real(dp),allocatable :: a(:), b(:)

    allocate (a(10))

    a = 1.d0
(*) b = a

end program
理解前一个代码自动分配
b
是否合适

program test_fail

   implicit none
   real(dp),allocatable :: a(:), b(:)
   integer              :: i

   allocate ( a(10) )

   a = 1.d0

   do i = 1, 10
      b(i) = a(i)
   enddo

end program
同样,在上述带有成形数组输入的子例程中,代码自动检测输入数组
a
的大小,并在子例程中分配自己的数组,并将其释放回其上一帧,这样理解是否正确

最后,将一个数组复制到另一个数组时,哪个更快

subroutine test_sub(a)

   implicit none

   real(dp),intent(inout) :: a(:,:)

   ...

end program

我没有太多的时间,所以这可能是相当压缩。还要注意的是,正如上面的一条评论所指出的(为了突出和子孙后代,复制到这里),代码和这个答案的有效性取决于版本(更多细节请参见)

正如你所发现的,声明

program speed2

  implicit none
  real(dp), allocatable :: a(:,:,:), b(:,:,:)
  integer               :: i, j, k

  allocate( a(10000,10000,10000), b(10000,10000,10000) )

  a = 1.d0

  do i = 1,10000
    do j = 1,10000
      do k = 1,10000
        b(i,j,k) = a(i,j,k)
      enddo
    enddo
  enddo

end program
b = a
b(i) = a(i)
将自动将数组
b
分配到与
a
相同的大小(和形状),并为其元素提供与
a
元素相同的值。这一切都符合标准。然而,声明

program speed2

  implicit none
  real(dp), allocatable :: a(:,:,:), b(:,:,:)
  integer               :: i, j, k

  allocate( a(10000,10000,10000), b(10000,10000,10000) )

  a = 1.d0

  do i = 1,10000
    do j = 1,10000
      do k = 1,10000
        b(i,j,k) = a(i,j,k)
      enddo
    enddo
  enddo

end program
b = a
b(i) = a(i)
左侧没有
数组
,它有
数组元素
,在这种情况下Fortran不会自动分配数组。这是不幸的,但(我相信)这是语言标准不要求编译器发现错误的情况之一——这就是为什么您在运行时会发现错误。您的编译器的行为似乎与我最近使用过的其他编译器一样—实际上,没有元素
b(1)
来分配
a(1)
的值,等等

至于语句中的子例程中的“分配”

program speed2

  implicit none
  real(dp), allocatable :: a(:,:,:), b(:,:,:)
  integer               :: i, j, k

  allocate( a(10000,10000,10000), b(10000,10000,10000) )

  a = 1.d0

  do i = 1,10000
    do j = 1,10000
      do k = 1,10000
        b(i,j,k) = a(i,j,k)
      enddo
    enddo
  enddo

end program
b = a
b(i) = a(i)
那不太一样。这里,
a
是假定的形状,只是假定传递给例程的相应参数的形状(和值)。《标准》没有说明这是如何做到的。大多数编译器不会复制数组(出于对Fortran程序员通常很重要的性能原因),但有些编译器可能会这样做,并且很容易构造示例,其中大多数编译器将复制作为参数传递的数据结构


至于最后两个codelet中哪一个更快?为什么不告诉我们?

High Performance Mark的答案是正确的,而且很有帮助,但是以稍微不同的方式重复一些要点可能会有一些好处

如前所述,在该答案和相关问题中,作业如下

real(dp),intent(inout) :: a(:,:) 
对于
b
未分配取决于是否使用Fortran 2003+规则。但是,如果未分配
b
,则分配给
b
的元素,如中所示

b = a
总是错误的(直到当前的语言版本)。每当数组元素出现在赋值的左侧(或变量引用中)时,下标的值必须在数组的边界内-但未分配的可分配数组没有定义的边界。1

这与Matlab等语言形成了对比,在Matlab中,如果存在超出当前数组大小范围的赋值,则数组会“增长”。Fortran(2003+)整个数组分配是一个例外,其中(重新)分配发生在整个数组完全位于右侧的情况下。在这样的整个数组分配上,所需的大小是已知的;通过逐元素分配,最终分配最初是未知的

关于子程序
test\u sub
的伪参数
a
,High Performance Mark的回答中有一点需要补充:这意味着子程序中的
a
可能与调用子程序的主程序中的数组共享某些方面。这确实可能包括之前给出的任何分配/存储

不过,为了完整起见:Fortran是那些语义不是逐行定义的语言之一。子程序的
隐藏了其他人可能感兴趣的内容。在这里面,
我们可以改变
a
的这种解释

b(i) = a(i)
突然间,
a
不是假定的形状,而是延迟的形状。我不会在这里讨论后果,但它们可以在其他问题和答案中找到

最后,在赋值速度方面,我们比较了整个数组赋值和数组所有元素上的循环(左侧正确分配)。Fortran的规则保证,对于左侧的数组,赋值是“在相应的数组元素上逐元素执行”,但“处理器可以按任何顺序逐元素执行赋值”

您的赋值(在大多数情况下)具有相同的效果,但编译器可能会根据需要重新排序或利用矢量化/并行化。然而,同样地,它可以对循环进行完全相同的分析(包括重新排序循环,正如伊恩·布什的评论所指出的那样,这可能会有所帮助,或者是为了响应OpenMP指令)。如果您想知道案例中性能的精确细节,您必须进行测试,但如果您使用手工制作的循环进行微优化,最终会惹恼某人,就像您使用整个数组分配惹恼其他人一样



1对于感兴趣的一方,请参见Fortran 2018 8.5.8.4和9.5.3.1。

让我扩展我的评论作为回答

1st代码截断编译是因为
b
在赋值
b=a
时采用
a
的形状,这是Fortran的标准功能

2nd代码段错误:未分配的数组无法索引,它没有形状或条目。这是一个错误

(在这个简单的例子中,编译器可以很容易地检测到这个错误。)