Fortran - 可分配派生类型的可分配数组

Posted

技术标签:

【中文标题】Fortran - 可分配派生类型的可分配数组【英文标题】:Fortran - Allocatable array of allocatable derived type 【发布时间】:2020-03-17 10:54:32 【问题描述】:

所以我过去 3-4 天一直在寻找,但找不到这个问题的答案。 它与特定派生类型的可分配数组有关。这都是计算流体动力学求解器的一部分。但是,实际应用并不重要。让我提供一些(快速)编程上下文。

假设我们有一个简单的模块,它定义了一个固定大小的派生类型,主程序分配了一个包含多种类型的数组:

module types

   integer, parameter  :: equations = 10

   type array_t
      double precision :: variable(equations) ! variables to solve
      double precision :: gradient(equations,3) ! gradient of variables in x,y,z direction
      double precision :: limiter(equations) ! limiter of variables
   end type

end module

program test

   use types

   implicit none

   type(array_t), allocatable :: array(:)
   integer :: elements

   elements = 100
   allocate(array(elements))

end program

当然,这段代码 sn-p 可以使用每个编译器进行编译。由于 array_t 的大小是固定的并且在编译时是已知的,因此我们只需在一行中分配结构 array(定义结构内的 array_t 重复次数)。

当涉及到内存位置时,变量将按如下方式存储:

array(1)%variable(1) ! element 1
array(1)%variable(2)
...
...
array(1)%gradient(1,1) ! the rest of this 2D array will be written column-major in fortran
array(1)%gradient(2,1)
array(1)%gradient(3,1)
...
...
array(1)%limiter(1)
array(1)%limiter(2)
...
...
array(2)%variable(1) ! element 2
array(2)%variable(2)
...
...

在本例中,我们设置参数equations=10。在求解器中,我们始终将此大小保留为最大值 (10):所有派生类型都具有求解器可能需要的最大维度。不幸的是,这意味着我们分配的内存可能比我们实际需要的更多——一些模拟可能只需要 5 或 6 个方程而不是 10 个。此外,派生类型维度保持固定在大小 10 的事实使求解器在求解较少方程时变慢 - 未使用的内存位置将减少内存带宽 -。

我想做的是利用具有 allocatable 属性的派生类型。这样,我可以只使用所需数量的方程(即 array_t 的维度)来分配结构,这些方程将在运行时(而不是在编译时)定义,并且会根据模拟参数而改变。

看看下面的代码sn-p:

module types

   integer, save:: equations

   type array_t
      double precision, allocatable :: variable(:) ! variables to solve
      double precision, allocatable :: gradient(:,:) ! gradient
      double precision, allocatable :: limiter(:) ! limiter of variables
   end type

end module

program test

   use types

   implicit none

   type(array_t), allocatable :: array(:)
   integer :: i,elements

   equations = 10
   elements = 100
   allocate(array(elements))
   do i=1,elements
      allocate(array(i)%variable(equations))
      allocate(array(i)%gradient(equations,3))
      allocate(array(i)%limiter(equations))
   enddo

end program

这是,到目前为止,唯一的方法我设法使它工作。求解器运行并收敛,这意味着语法不仅可编译而且等效于使用固定大小。

但是,即使对于相同数量的方程,这种方法的求解器速度也会明显变慢

这意味着存在内存错位。根据测量的运行时间,变量的存储方式似乎与使用固定大小时不同。

在第二种方法中,变量如何存储在全局内存中?我想实现与第一种方法相同的模式。我感觉像是分配结构的第一行

allocate(array(elements))

不知道要分配什么,因此要么分配一大块内存(以适应稍后出现的可分配类型),要么仅将索引 array(1) 分配给 array(elements) 而没有其他内容(这意味着结构的实际内容稍后存储在循环内)。

如何让第二种方法像第一种方法一样存储变量?

编辑 #1

由于参数化派生类型得到了一些关注,我认为发布一些额外的细节会很有用。

参数化派生类型将在数组分配在主程序内部的情况下工作(如我发布的示例代码)。

然而,我的“真实世界”案例更像是这样的:

(file_modules.f90)
module types

   integer, parameter  :: equations = 10

   type array_t
      double precision :: variable(equations) ! variables to solve
      double precision :: gradient(equations,3) ! gradient pf variables
      double precision :: limiter(equations) ! limiter of variables
   end type

end module

module flow_solution
   use types
   type (array_t), allocatable, save :: cell_solution(:)
end module

(file_main.f90)
program test

   use flow_solution

   implicit none
   integer :: elements

   elements = 100
   allocate(cell_solution(elements))

end program

这些(如您所料)通过 Makefile 单独编译和链接。 如果我使用参数化派生类型,则无法编译模块文件,因为在编译时不知道类型的大小“n”。

编辑 #2

有人建议我提供具有参数化派生类型的工作和非工作代码示例。

工作示例

module types

   integer, parameter  :: equations=10

   type array_t(n)
      integer, len     :: n
      double precision :: variable(n) ! variables to solve
      double precision :: gradient(n,n) ! gradient
      double precision :: limiter(n) ! limiter of variables
   end type

end module

module flow_solution
    use types
    type(array_t(equations)), allocatable, save :: flowsol(:)
end module

program test

   use flow_solution

   implicit none

   integer :: i,elements

   elements = 100 
   allocate(flowsol(elements))

end program

非工作示例

module types

   integer, save       :: equations

   type array_t(n)
      integer, len     :: n
      double precision :: variable(n) ! variables to solve
      double precision :: gradient(n,n) ! gradient
      double precision :: limiter(n) ! limiter of variables
   end type

end module

module flow_solution
    use types
    type(array_t(equations)), allocatable, save :: flowsol(:)
end module

program test

   use flow_solution

   implicit none

   integer :: i,elements

   equations = 10
   elements = 100 
   allocate(flowsol(elements))

end program

编译器(ifort)错误:

test.f90(16): error #6754: An automatic object must not appear in a SAVE statement or be declared with the SAVE attribute.   [FLOWSOL]
    type(array_t(equations)), allocatable, save :: flowsol(:)
---------------------------------------------------^
test.f90(16): error #6841: An automatic object must not appear in the specification part of a module.   [FLOWSOL]
    type(array_t(equations)), allocatable, save :: flowsol(:)
---------------------------------------------------^
compilation aborted for test.f90 (code 1)

我应该以不同的方式声明/分配数组吗?

【问题讨论】:

您可能正在寻找 Fortran 的参数化派生类型。我没有时间写答案,但是关于 SO 上的这个主题有一些 Q 和 As,还有很多其他资源在网上。 给定类似type(array_t(:)), allocatable :: cell_solution(:) 的相应分配语句allocate(array_t(5) :: cell_solution(100)) 似乎是合适的。这就是 array_tlength 参数化类型的地方(此注释中未显示)。这种长度参数化的类型在这里似乎适用。 感谢您的意见!你的建议是可编译的。但是,我必须在求解器中实现它,看看它是否正常工作。问题是我在使用可分配类型方法时遇到了内存错位问题。我希望这种方法以正确的方式存储变量。 如果您关心派生类型在内存中的布局,您可能需要调查sequence 语句。 【参考方案1】:

麻烦的线是

type(array_t(equations)), allocatable, save :: flowsol(:)

在这里,您希望将长度参数化类型作为保存对象。作为编译器对象,保存的对象不能是自动对象。此外,您不能在模块范围内拥有自动对象。

现在,为什么flowsol 是一个自动对象?它是自动的,因为它的类型是 array_t(equations)equations 不是常量。相反,您希望延迟类型参数(就像数组的形状一样):

type(array_t(:)), allocatable, save :: flowsol(:)

当你来分配这样一个对象时你需要提供长度类型参数值:

allocate(array_t(equations) :: flowsol(elements))

如您所见,当equations 是(命名的)常量时,事情会更快乐。在这种情况下,不会声明自动对象。

【讨论】:

不幸的是,这个选项也很慢。乍一看,它似乎比可分配方法还要慢,但是我没有时间比较两者。我可以肯定的是,它肯定比原始方法(具有固定方程=10 作为参数的方法)慢得多。由于这个结构是一个相对较大的求解器的一部分,我认为我不可能发布一个展示我所指的减速的代码示例。 对于上下文,我唯一改变的是我将“方程式”变量更改为“整数,保存”而不是“整数,参数”。相应的类型以长度 n 参数化,求解器初始化中的分配如您所建议的那样。请注意,我没有更改任何其他结构,也没有在其他任何地方使用这种类型。即使这样,求解器的速度也几乎慢了 100%。 大幅放缓是出乎意料的,但正如您所说,在 SO 问题的范围内没有什么可看的。也许作为一种测试,您可以将类型 kind 参数化(甚至可能为 10),看看是否仍然存在减速。

以上是关于Fortran - 可分配派生类型的可分配数组的主要内容,如果未能解决你的问题,请参考以下文章

在派生类型中使用可分配的目标变量

在派生类型中使用可分配的目标变量

指向包含可分配数组的派生类型的指针

可分配的用户派生类型

派生类型中的可变长度数组

确定内存中的 Fortran 派生类型大小