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

时间:2015-07-15 14:23:28

标签: memory-management fortran declaration allocation

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

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

allocate(alloc_array(n))
...[code]...

我非常熟悉分配的基础(而不是高级技术)知道分配允许您在代码中间更改数组的大小(如指出的那样)在this question),但我有兴趣考虑你不需要来改变数组大小的情况;它们可能被传递给其他子程序进行操作,但代码和任何子程序中两个变量的唯一目的是保存维数n数组的数据(并且可能更改数据,但不能更改大小)

(1)内存使用有什么不同吗?我不是低级别程序的专家,但我对它们如何重要以及它们如何影响更高级别的程序非常了解水平编程(有点体验我一直在谈论:一旦尝试在fortran中运行大代码我遇到了一个我不明白的错误,系统管理员告诉我"哦,是的,你可能是使堆栈饱和;尝试在运行的脚本中添加这一行&#34 ;;任何让我深入了解如何在实际编码时考虑这些内容并且不必在以后修补它们的任何内容都受到欢迎。人们告诉我,它可能依赖于许多其他的东西,如编译器或架构,但我从这些回复中解释说,他们并不完全确定这是怎么回事。它是如此完全依赖于多种因素,还是编码中的默认/预期行为可能会被可选的编译关键字或系统首选项所覆盖?

(2)子程序是否有不同的接口需求?再次,不是专家,但在此之前发生过我因为我声明子程序变量的方式,我最终不得不将子程序放在一个模块中。我已经了解这可能会有所不同,这取决于我是否使用对可分配变量特殊的东西。我正在考虑这样一种情况,即我对变量所做的一切都可以通过allocatables和automatics来完成,而不是故意使用任何特定的allocatables(除了在使用之前的分配,即)。

最后,如果这是有用的:我问的原因是因为我们正在开发一个小组,我们最近注意到不同的人以不同的方式使用这两个声明,我们需要确定这是否可以留给个人偏好,或者是否有任何理由为什么设置明确标准(以及如何设置该标准)可能是个好主意。我不需要非常详细的答案,我正在努力确定这是否是我应该做的研究,要小心我们如何使用它以及研究的主要方面。

虽然我有兴趣知道"有趣的技巧"我可以留下那些可能的未来后续问题并将重点放在严格的功能差异上(意思是:我明确告诉编制者要做的事情)用我的代码)。我提到的两件事是由于以前的经历我能想到的,但是我想念的任何其他重要的事情都应该考虑,请提及它们。

2 个答案:

答案 0 :(得分:4)

由于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

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

gfortran -O3 test.f90 sub.f90

并测试nsizemax的不同值,同时保持nloop = 10 ^ 7。结果如下表所示(时间以秒为单位,由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

因此,当使用-O3时,两次调用的整体时间几乎相同(但请参阅编辑以了解不同的选项)。接下来,我用ifort编译为

[O3]  ifort -O3 test.f90 sub.f90
or
[O3h] ifort -O3 -heap-arrays test.f90 sub.f90

在前一种情况下,自动数组存储在堆栈中,而当连接-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

因此对于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

修改

感谢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

这里,-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

显示十次测量的平均值。此表演示如果包含-fstack-arrays,则小n的执行时间会变短。这一趋势与上述ifort的结果一致。

然而,应该提到的是,上述比较可能对应于"最佳情况"突出它们之间差异的场景,因此在实践中时间差异可以小得多。例如,我通过使用其他一些程序(包括小型和大型数组)比较了上述选项的时间,结果不受堆栈选项的影响。当然,结果还应取决于机器架构以及编译器。所以你的里程可能会有所不同。

答案 1 :(得分:1)

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

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

同样在链接问题中,在分配语句之后,两个数组的大小都为n。我会在这里回答,这些仍然是两件非常不同的事情。当然,可分配数组可以更改其分配状态并移动其分配。我会把这些(大部分)都放在这个答案的范围之外。当然,可分配数组一旦定义就不需要更改这些内容。

内存使用情况

对于之前修订的问题,部分争议的是内存使用概念的定义是多么不明确。作为标准,Fortran告诉我们两个阵列的大小相同,并且它们具有相同的存储布局,并且都是连续的。除此之外,您会听到很多以下条款:特定于实现依赖于处理器

在评论中,您对ifort表示了兴趣。所以我不会走得太远,我会坚持使用那个编译器并提示要考虑什么。

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

  

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

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

  

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

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

  

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

由于n不是常量表达式,因此无论指定的大小如何,都可以使用此选项将数组automatic_array放在堆上。

可能还有更多要说的内容,但如果我尝试的话,这可能会太长。

界面需求

子例程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))

最后,自动对象的大小条件非常严格。 n这里显然是允许的,但在分配是唯一合理的方式之前,事情不必复杂得多。取决于想要使用block构造的数量。

同时接受IanH的评论:如果我们有一个非常大的n,自动对象可能会导致崩溃和灼伤。使用allocatable,可以使用stat=选项与编译器运行时达成一些友好协议。