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

4

我过去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

当然,这段代码片段可以使用任何编译器进行编译。由于array_t的大小是固定的,而且在编译时已知,因此我们只需要在单行中分配结构体array(定义结构体内的数组重复次数)。

关于内存位置,变量将按以下方式存储:

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意味着当我们解决较少的方程时求解器变慢——未使用的内存位置将减少内存带宽。
我想做的是利用具有可分配属性的派生类型。这样,我可以仅使用运行时定义的所需方程数(即数组_t的维度),其将根据模拟参数进行更改,并分配结构体。
请看下面的代码片段:
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))

不知道该分配什么,所以它要么分配一个大块内存(以适应稍后将出现的可分配类型),要么只分配索引数组(1)到数组(元素),什么也不分配(这意味着结构体的实际内容稍后在循环内部存储)。

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

编辑 #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'在编译时不可知。

第二次编辑

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

工作示例:

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)

我需要在不同的方式中声明/分配数组吗?

2
你可能正在寻找Fortran的参数化派生类型。我没有时间写答案,但是在这里有一些关于这个主题的问答,以及其他许多网络资源。 - High Performance Mark
给定类似于 type(array_t(:)), allocatable :: cell_solution(:) 这样的内容,相应的分配语句 allocate(array_t(5) :: cell_solution(100)) 似乎是合适的。这里的 array_t 是一个 长度 参数化类型(在此注释中未显示)。这种长度参数化类型似乎适用于这里。 - francescalus
感谢您的输入!您的建议是可编译的。但是,我必须在求解器中实现它并查看其是否正常工作。问题在于,我使用可分配类型方法时遇到了内存不对齐的问题。我希望这种方法可以正确地存储变量。 - SlapGas Unseen
如果您关心派生类型在内存中的布局,您可能需要调查sequence语句。 - High Performance Mark
1个回答

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))

正如您所看到的,当“方程式”是一个(命名的)常量时,事情会更加愉快。在这种情况下,不会声明自动对象。

很不幸,这个选项也非常慢。乍一看,它似乎比可分配方法还要慢,但我没有时间比较两者。我确定的是它绝对比原来的方法(参数为固定方程数=10)慢得多。由于这个结构是相对较大求解器的一部分,我认为我无法发布一个展示我所说的减速的代码示例。 - SlapGas Unseen
为了更好的上下文,我只改变了“equations”变量,将其从“integer, parameter”更改为“integer, save”。相应的类型是参数化的,长度为n,并且求解器初始化时的分配方式如您所建议的那样。请注意,我没有改变任何其他结构,也没有在其他地方使用此类型。即使如此,求解器几乎慢了100%。 - SlapGas Unseen
一个大的减速是意外的,但正如你所说,在SO问题的范围内没有太多可以看的。也许作为一个测试,你可以使类型_kind_参数化(甚至是10),看看是否仍然会减速。 - francescalus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接