可分配的阵列性能

时间:2011-09-09 08:50:04

标签: performance memory-management fortran mpi fortran-common-block

有一个mpi版本的程序,它使用COMMON块来存储在代码中到处使用的数组。不幸的是,没有办法在COMMON块大小中声明数组,只有运行时才知道它们。因此,作为一种解决方法,我决定将这些数组移动到接受ALLOCATABLE数组的模块中。也就是说,COMMON块中的所有数组都消失了,而是使用了ALLOCATE。所以,这是我在程序中唯一改变的东西。不幸的是,程序的性能很糟糕(与COMMON块实现相比)。至于mpi-settings,每个计算节点上都有一个mpi-process,每个mpi-process都有一个单独的线程。 我在这里问了similar问题,但是没想到(不明白:))它如何应用于我的情况(每个进程都有一个线程)。我感谢任何帮助。

这是一个简单的例子,它说明了我在说什么(下面是伪代码):

“SOURCE FILE”:

SUBROUTINE ZEROSET()
   INCLUDE 'FILE_1.INC'
   INCLUDE 'FILE_2.INC'
   INCLUDE 'FILE_3.INC'
   ....
   INCLUDE 'FILE_N.INC'

   ARRAY_1 = 0.0
   ARRAY_2 = 0.0
   ARRAY_3 = 0.0
   ARRAY_4 = 0.0
   ...
   ARRAY_N = 0.0
END SUBROUTINE

正如您所看到的,ZEROSET()没有并行或MPI的东西。 FILE_1.INC,FILE_2,...,FILE_N.INC是在COMMON块中定义ARRAY_1,ARRAY_2 ... ARRAY_N的文件。像这样的东西

REAL ARRAY_1
COMMON /ARRAY_1/ ARRAY_1(NX, NY, NZ)

其中NX,NY,NZ是在PARAMETER指令的帮助下描述的明确定义的参数。 当我使用模块时,我只是销毁了所有COMMON块,因此FILE_I.INC看起来像

REAL, ALLOCATABLE:: ARRAY_I(:,:,:)

然后将上面的“INCLUDE'FILE_I.INC'”语句改为“USE FILE_I”。实际上,当执行并行程序时,一个特定的进程不需要整个(NX,NY,NZ)域,所以我计算参数然后分配ARRAY_I(仅ONCE!)。

子程序ZEROSET()使用COMMON块执行0.18秒,使用模块执行0.36(运行时计算数组的维数)。因此,性能恶化了两倍。

我希望现在一切都清楚了。我非常感谢你的帮助。

3 个答案:

答案 0 :(得分:3)

在模块中使用可分配数组通常会损害性能,因为编译器在编译时不知道大小。使用此代码的许多编译器将获得更好的性能:

   subroutine X
   use Y  ! Has allocatable array A(N,N) in it
   call Z(A,N)
   end subroutine

   subroutine Z(A,N)
   Integer N
   real A(N,N)
   do stuff here
   end

然后这段代码:

   subroutine X
   use Y  ! Has allocatable array A(N,N) in it
   do stuff here
   end subroutine

编译器将知道数组是NxN并且do循环超过N并且能够利用这一事实(大多数代码在数组上以这种方式工作)。此外,在“do stuff here”中的任何子例程调用之后,编译器将不得不假设数组“A”可能已更改内存中的大小或移动位置并重新检查。这会导致优化。

这可以让你恢复大部分表现。

公共块也位于内存中的特定位置,并且还允许进行优化。

答案 1 :(得分:0)

当谈到使用数组的fortran性能时,我能想到这些原因:

    堆栈VS堆上的
  1. 数组,但我怀疑这会对性能造成巨大影响。
  2. 将数组传递给子程序,因为最好的方法取决于数组,请参阅using arrays efficiently上的此页

答案 2 :(得分:0)

实际上我猜,你的问题是,与堆栈与堆内存相结合,确实是基于编译器优化。根据您使用的编译器,它可能会执行更高效的内存消隐,对于固定的内存块,它甚至不需要在子例程中检查它的范围和位置。因此,在固定大小的阵列中,几乎不会涉及任何开销。 这个例程经常被调用,或者你为什么关心这些0.18秒? 如果确实相关,最好的选择是完全摆脱0设置,而是例如分离第一个迭代循环并将其用于初始化,这样你就不必引入额外的内存访问,只需用于初始化为0.但是它会复制一些代码......