【问题标题】:Allocatable arrays performance可分配数组性能
【发布时间】:2011-11-13 14:58:43
【问题描述】:

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

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

“源文件”:

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 秒(当数组的维度是在运行时计算时)。因此,性能恶化了两倍。

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

【问题讨论】:

  • 这应该与并行执行有什么关系?在串行执行中性能下降是否已经不可见?你多久分配一次?虚拟参数是否具有可分配属性?
  • 您的问题不太可能与 mpi 相关。你是在做共享内存还是分布式内存?您可能遇到了内存瓶颈,但如果您做的一切都正确,则情况并非如此,因为代码的 COMMON 块版本可以正常工作。请包括您分配的方式和内容的示例代码。如果您分配和取消分配您的数组一次(分别是程序的开始和结束),您应该不会看到性能下降。
  • 性能“糟糕”不是很量化,慢了多少?如上所述,请先在串行运行中验证您的问题。
  • 伙计们,我很确定这个问题与并行编程或 MPI 无关。这就是为什么我的问题标题不包含与 MPI 相关的单词。我只是想准确并描述程序的环境。这是一个简单的例子,它说明了我在说什么(抱歉没有早点提供)找不到如何在评论中显示一段代码,所以我只在我的初始帖子中描述了这个例子

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


【解决方案1】:

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

  1. 堆栈 VS 堆上的数组,但我怀疑这会对性能产生巨大影响。
  2. 将数组传递给子例程,因为这样做的最佳方式取决于数组,请参阅using arrays efficiently 上的此页面

【讨论】:

  • 链接已损坏,所以我提交了一个编辑,我认为是同一篇文章。如果我错了,请纠正它。
【解决方案2】:

实际上我猜,您的问题是,结合堆栈与堆内存,确实是基于编译器优化。根据您使用的编译器,它可能会执行一些更有效的内存消隐,并且对于固定的内存块,它甚至不需要检查它在子例程中的范围和位置。因此,在固定大小的数组中,几乎不会涉及任何开销。 这个例程是不是经常被调用,或者你为什么关心这些 0.18 s? 如果确实相关,最好的选择是完全摆脱 0 设置,而是例如分离第一个迭代循环并将其用于初始化,这样您就不必引入额外的内存访问,只需使用 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”可能已经改变了大小或移动了内存中的位置并重新检查。这会扼杀优化。

    这应该可以让您恢复大部分性能。

    公共块也位于内存中的特定位置,这也允许优化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-10
      • 2013-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-19
      • 1970-01-01
      相关资源
      最近更新 更多