Fortran - размещаемый массив выделяемого производного типа

Итак, я искал последние 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_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)
...
...

В этом примере мы устанавливаем параметр sizes = 10. В решающей программе мы всегда оставляем этот размер максимальным (10): все производные типы имеют максимальный размер, который может потребоваться решающей программе. К сожалению, это означает, что мы можем выделить больше памяти, чем нам на самом деле нужно - для некоторых симуляций может потребоваться всего 5 или 6 уравнений вместо 10 -. Кроме того, тот факт, что размерность производного типа остается фиксированной на размере 10, замедляет работу решателя, когда мы решаем меньшее количество уравнений - неиспользуемые ячейки памяти уменьшают пропускную способность памяти.

Я хочу использовать производные типы с атрибутом allocatable. Таким образом, я могу выделить структуры, используя только необходимое количество уравнений (то есть размеры array_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 типа неизвестен во время компиляции.

ИЗМЕНИТЬ №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)

Следует ли объявлять / размещать массивы по-другому?


person SlapGas Unseen    schedule 21.11.2019    source источник
comment
Вероятно, вы ищете параметризованные производные типы Фортрана. У меня нет времени писать ответ, но есть несколько вопросов и ответов по теме здесь, на SO, и множество других ресурсов в сети.   -  person High Performance Mark    schedule 21.11.2019
comment
Учитывая что-то вроде type(array_t(:)), allocatable :: cell_solution(:), соответствующий оператор распределения allocate(array_t(5) :: cell_solution(100)) может показаться подходящим. Здесь array_t - это тип с параметром length (не показан в этом комментарии). Такие типы с параметризацией длины кажутся здесь применимыми.   -  person francescalus    schedule 21.11.2019
comment
Спасибо за вклад! Ваше предложение можно составить. Однако мне придется реализовать его в решателе и посмотреть, правильно ли он работает. Дело в том, что у меня были проблемы с рассогласованием памяти при использовании подхода с размещаемыми типами. Я надеюсь, что этот подход хранит переменные должным образом.   -  person SlapGas Unseen    schedule 21.11.2019
comment
Если вас беспокоит расположение производных типов в памяти, вы можете изучить оператор sequence.   -  person High Performance Mark    schedule 21.11.2019


Ответы (1)


Проблемная линия

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

Здесь вы хотите, чтобы тип с параметризацией длины был сохраненным объектом. Как объекты компилятора, сохраненный объект не может быть автоматическим объектом. Кроме того, у вас не может быть автоматического объекта в области видимости модуля.

Итак, почему flowsol автоматический объект? Это автоматически, потому что его тип array_t(equations): equations не является константой. Вместо этого вы хотите, чтобы параметр типа был отложен (так же, как и форма массива):

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

Когда вы приступаете к выделению такого объекта, вам необходимо указать значение параметра типа length:

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

Как видите, когда equations является (именованной) константой, дела обстоят лучше. В таком случае автоматический объект не объявляется.

person francescalus    schedule 21.11.2019
comment
К сожалению, этот вариант тоже очень медленный. На первый взгляд мне кажется, что это даже медленнее, чем подход с размещением, однако у меня не было времени сравнить их. В чем я уверен, так это в том, что он определенно, значительно медленнее, чем исходный подход (тот, который имеет фиксированное уравнение = 10 в качестве параметра). Поскольку эта структура является частью относительно большого решателя, я не думаю, что у меня есть возможность опубликовать пример кода, демонстрирующий замедление, о котором я говорю. - person SlapGas Unseen; 21.11.2019
comment
Что касается контекста, единственное, что я изменяю, это то, что я изменил переменную уравнений на целочисленный параметр save вместо целого. Соответствующий тип параметризован длиной n, а распределение при инициализации решателя такое, как вы предложили. Обратите внимание, что я не изменяю никакую другую структуру и не использую этот тип где-либо еще. Даже в этом случае решатель работает почти на 100% медленнее. - person SlapGas Unseen; 21.11.2019
comment
Большое замедление является неожиданным, но, как вы говорите, не на что смотреть в рамках вопроса SO. Возможно, в качестве теста вы могли бы сделать тип kind -параметрическим (может быть, даже 10), чтобы увидеть, есть ли по-прежнему замедление. - person francescalus; 21.11.2019