本地可分配数组和自动数组之间的区别

Nor*_*ico 4 fortran memory-management allocation declaration

我对alloc_arrayautomatic_array以下摘录之间的区别感兴趣:

subroutine mysub(n)
integer, intent(in)  :: n
integer              :: automatic_array(n)
integer, allocatable :: alloc_array(:)

allocate(alloc_array(n))
...[code]...
Run Code Online (Sandbox Code Playgroud)

我对分配的基础知识非常熟悉(对高级技术的了解不多),知道分配使您可以在代码中间更改数组的大小(如本问题所指出),但是我对此很感兴趣在考虑不需要更改数组大小的情况下;它们可能会传递给其他子例程以进行操作,但是代码和任何子例程中的变量的唯一目的是保存维数组的数据n(并且可能会更改数据,但不会更改大小)。

(1)内存使用是否有差异?我不是底层程序的专家,但是我对它们的重要性以及它们如何影响高层编程的知识非常了解(我正在谈论的经验是:曾经尝试在fortran中运行大代码我遇到了一个我不明白的错误,sysadmin告诉我:“哦,是的,您可能正在饱和堆栈;请尝试在运行的脚本中添加此行”;任何使我深刻了解在实际编码时如何考虑这一点的东西并且不必在以后修补它们)。有人告诉我,这可能取决于许多其他因素,例如编译器或体系结构,但是我从这些响应中解释说,他们并不完全确定这是怎么回事。

(2)子例程会具有不同的接口需求吗?再说一次,不是专家,但是在那之前,由于我声明子例程变量的方式发生在我身上,最终我不得不将子例程放入模块中。我被理解为这可能会有所不同,具体取决于我是否使用可分配变量的特殊内容。我正在考虑这样一种情况,我对变量所做的所有事情都可以通过可分配的和自动的方式完成,而不是有意地使用特定于可分配的任何东西(除了使用前的分配以外)。

最后,如果这是有用的:我要问的原因是因为我们正在一个团队中发展,最近我们注意到不同的人以不同的方式使用这两个声明,因此我们需要确定这是否可以保留个人喜好或设置清晰标准(以及如何设置该标准)的好主意的原因。我不需要非常详细的答案,我正在尝试确定这是否是我应该进行研究的内容,以谨慎使用它以及应在研究的哪些方面进行。

尽管我很想知道“有趣的把戏”,而不是分配所能完成的,但与大小可变性的需求没有直接关系,但我将那些留给未来可能的后续问题,并将重点放在严格的功能上。差异(意思是:我明确告诉编译器处理我的代码的内容)。我提到的两个项目是我根据以前的经验可以想到的,但是我想念的任何其他重要的项目都请考虑。

fra*_*lus 6

为了清楚起见,我将简要提及术语。这两个数组都是局部变量和秩为 1 的数组。

  • alloc_array 是一个可分配的数组;
  • automatic_array 是一个显式形状的自动对象。

作为局部变量,它们的范围是过程的范围。自动数组和未保存的可分配数组在过程执行完成时结束(可分配数组被释放);无法保存自动对象,并且在执行完成时不会释放已保存的可分配对象。

同样,如在链接问题中一样,在分配语句之后,两个数组的大小都为n。这仍然是两个非常不同的事情。当然,可分配数组可以改变其分配状态并移动其分配。我将把这两个(大部分)都排除在这个答案的范围之外。当然,一个可分配的数组一旦被分配就不必改变这些东西。

内存使用情况

关于该问题的先前修订版的部分争议在于内存使用的概念定义不明确。Fortran,作为一种语言定义,告诉我们两个数组大小相同,存储布局相同,并且都是连续的。除此之外,您会听到很多术语:实现特定处理器相关

在评论中,您表达了对 ifort 的兴趣。所以我不会走得太远,我会坚持使用那个编译器。其他编译器也有类似的概念,尽管名称和选项不同。

通常,ifort 会将自动对象和临时数组放入堆栈。有一个(默认)编译器选项-no-heap-arrays被描述为有效

编译器将自动数组和临时数组放入堆栈存储区。

使用替代选项-heap-arrays可以稍微控制一下:

此选项将自动数组和为临时计算创建的数组放在堆上而不是堆栈上。

有可能控制选择堆/堆栈的大小阈值(在编译时已知时):

如果编译器在编译时无法确定大小,它总是将自动数组放在堆上。

由于n不是常量,automatic_array因此无论指定的大小如何,都希望使用此选项在堆上。为了n在编译时确定数组的大小 ,编译器可能需要进行大量的代码分析,即使可能。

可能还有更多要说的,但如果我尝试,这个答案会太长。但是,需要注意的一件事是,可以预期自动本地对象和(Fortran 90 之后)可分配的本地对象不会泄漏内存。

接口需求

子程序的接口要求没有什么特别之处mysub:局部变量对此没有影响。任何对隐式接口感到满意的程序单元调用。您要问的是如何使用两个本地数组。

这在很大程度上归结为两个数组可以用于什么用途。

如果第二个过程的虚拟参数具有 allocatable 属性,则只有此处的可分配数组可以传递给该过程。它还需要有一个显式接口。无论过程是否更改分配,都是如此。

当然,两个数组都可以作为参数传递给没有 allocatable 属性的虚拟参数,这样我们就没有不同的接口要求。

无论如何,当分配状态等没有变化时,为什么要将参数传递给可分配的虚拟对象?有很好的理由:

  • 过程中可能有一个代码路径确实发生了分配更改(例如由开关控制);
  • 可分配的虚拟参数也通过边界;
  • 等等。,

如果子程序有规范,则第二个更明显

subroutine mysub(n)
integer, intent(in)  :: n
integer              :: automatic_array(2:n+1)
integer, allocatable :: alloc_array(:)

allocate(alloc_array(2:n+1))
Run Code Online (Sandbox Code Playgroud)

最后,自动对象对其大小有非常严格的条件。 n这里显然是允许的,但在分配是唯一可行的方法之前,事情不必复杂得多。取决于人们想要玩多少block构造。

还有来自 IanH 的评论:如果我们有一个非常大n的自动对象,很可能会导致崩溃和烧毁。使用 allocatable,可以使用stat=选项与编译器运行时达成某种友好的协议。

  • `stat=` 对应于 `allocate` 语句。如果没有它,问题会导致错误终止,有了它,您就需要检查状态(分配仍然失败),但它允许从程序正常退出,或者通过替代路径。 (2认同)

roy*_*vib 5

因为gfortran或ifort + Linux(x86_64)是用于HPC的最受欢迎的组合,所以我对这些组合的本地可分配阵列与自动阵列进行了一些性能比较。使用的CPU是Xeon E5-2650 v2@2.60GHz,编译器是gfortran4.8.2和ifort14.0。测试程序如下。

In test.f90:

!------------------------------------------------------------------------           
subroutine use_automatic( n )
    integer :: n

    integer :: a( n )   !! local automatic array (with unknown size at compile-time)
    integer :: i

    do i = 1, n
        a( i ) = i
    enddo

    call sub( a )
end

!------------------------------------------------------------------------           
subroutine use_alloc( n )
    integer :: n

    integer, allocatable :: a( : )  !! local allocatable array                      
    integer :: i

    allocate( a( n ) )

    do i = 1, n
        a( i ) = i
    enddo

    call sub( a )

    deallocate( a )  !! not necessary for modern Fortran but for clarity                  
end

!------------------------------------------------------------------------           
program main
    implicit none
    integer :: i, nsizemax, nsize, nloop, foo
    common /dummy/ foo

    nloop = 10**7
    nsizemax = 10

    do i = 1, nloop
        nsize = mod( i, nsizemax ) + 1

        call use_automatic( nsize )
        ! call use_alloc( nsize )                                                   
    enddo

    print *, "foo = ", foo   !! to check if sub() is really called
end

In sub.f90:

!------------------------------------------------------------------------
subroutine sub( a )
    integer a( * )
    integer foo
    common /dummy/ foo

    foo = a( 1 )
ends
Run Code Online (Sandbox Code Playgroud)

在上面的程序中,我尝试避免通过将sub()放在另一个文件中并使接口隐式来消除编译器优化,该优化器消除了a(:)本身(即无操作)。首先,我使用gfortran将程序编译为

gfortran -O3 test.f90 sub.f90
Run Code Online (Sandbox Code Playgroud)

并在保持nloop = 10 ^ 7的情况下测试了不同的nsizemax值。结果在下表中(时间以秒为单位,由time命令多次测量)。

nsizemax    use_automatic()    use_alloc()
10          0.30               0.31               # average result
50          0.48               0.47
500         1.0                0.90
5000        4.3                4.2
100000      75.6               75.7
Run Code Online (Sandbox Code Playgroud)

因此,使用-O3时,两个调用的总体时序似乎几乎相同(但请参阅“编辑”了解不同的选项)。接下来,我将ifort编译为

[O3]  ifort -O3 test.f90 sub.f90
or
[O3h] ifort -O3 -heap-arrays test.f90 sub.f90
Run Code Online (Sandbox Code Playgroud)

在前一种情况下,自动数组存储在堆栈中,而在附加-heap-arrays时,该数组存储在堆中。得到的结果是

         use_automatic()    use_alloc()
         [O3]    [O3h]      [O3]    [O3h]
10       0.064   0.39       0.48    0.48
50       0.094   0.56       0.65    0.66
500      0.45    1.03       1.12    1.12
5000     3.8     4.4        4.4     4.4
100000   74.5    75.3       76.5    75.5
Run Code Online (Sandbox Code Playgroud)

因此,对于ifort而言,在主要使用相对较小的数组时,使用自动数组似乎是有益的。另一方面,gfortran -O3没什么区别,因为两个数组的处理方式相同(请参阅“编辑”以获取更多详细信息)。

附加比较:

以下是适用于Linux的Oracle Fortran编译器12.4(与f90 -O3一起使用)的结果。总体趋势似乎相似。对于小n,自动数组的速度更快,这表示内部使用堆栈。

nsizemax    use_automatic()    use_alloc()
10          0.16               0.45
50          0.17               0.62
500         0.37               0.97
5000        2.04               2.67
100000      65.6               65.7
Run Code Online (Sandbox Code Playgroud)

编辑

感谢弗拉基米尔(Vladimir)的评论,事实证明gfortran -O3将自动数组(在编译时大小未知)放在堆上。这解释了为什么use_automatic()和use_alloc()在上面没有任何区别。因此,我在下面的不同选项之间做了另一个比较:

[O3]  gfortran -O3
[O5]  gfortran -O5
[O3s] gfortran -O3 -fstack-arrays
[Of]  gfortran -Ofast                   # this includes -fstack-arrays
Run Code Online (Sandbox Code Playgroud)

在这里,-fstack-arrays意味着编译器将所有大小未知的本地数组放入堆栈。请注意,默认情况下,此标志已启用-Ofast。得到的结果是

nsizemax    use_automatic()               use_alloc()
            [Of]   [O3s]  [O5]  [O3]     [Of]  [O3s]  [O5]  [O3]
10          0.087  0.087  0.29  0.29     0.29  0.29   0.29  0.29
50          0.15   0.15   0.43  0.43     0.45  0.44   0.44  0.45
500         0.57   0.56   0.84  0.84     0.92  0.92   0.92  0.92
5000        3.9    3.9    4.1   4.1      4.2   4.2    4.2   4.2
100000      75.1   75.0   75.6  75.6     75.6  75.3   75.7  76.0
Run Code Online (Sandbox Code Playgroud)

显示了十次测量的平均值。该表表明,如果-fstack-arrays包含,则小n的执行时间会缩短。这种趋势与上面关于ifort获得的结果一致。

但是,应该提到的是,以上比较可能对应于“最佳情况”场景,该场景突出了它们之间的差异,因此在实践中,时序差异可能会小得多。例如,我通过使用其他程序(涉及小阵列和大阵列)比较了上述选项的时序,并且结果不受堆栈选项的影响很大。当然,结果还取决于机器体系结构以及编译器。因此,您的里程可能会有所不同。