Использование памяти в фортране при использовании массива производного типа с указателем

13

В этом примере программы я делаю одно и то же (по крайней мере, мне так кажется) двумя разными способами. Я запускаю это на своем компьютере с Linux и наблюдаю за использованием памяти с помощью top. Используя gfortran, я обнаружил, что в первом случае (между «1» и «2») используемая память составляет 8,2 ГБ, а во втором (между «2» и «3») использование памяти составляет 3,0 ГБ. С компилятором Intel разница еще больше: 10 ГБ против 3 ГБ. Это кажется чрезмерным штрафом за использование указателей. Почему это происходит?

program test
implicit none

  type nodesType
    integer:: nnodes
    integer,dimension(:),pointer:: nodes 
  end type nodesType

  type nodesType2
    integer:: nnodes
    integer,dimension(4):: nodes 
  end type nodesType2

  type(nodesType),dimension(:),allocatable:: FaceList
  type(nodesType2),dimension(:),allocatable:: FaceList2

  integer:: n,i

  n = 100000000

  print *, '1'
  read(*,*)
  allocate(FaceList(n))
  do i=1,n
    FaceList(i)%nnodes = 4
    allocate(FaceList(i)%nodes(4))
    FaceList(i)%nodes(1:4) = (/1,2,3,4/)
  end do
  print *, '2'
  read(*,*)

  do i=1,n
    deallocate(FaceList(i)%nodes)
  end do
  deallocate(FaceList)

  allocate(FaceList2(n))
  do i=1,n
    FaceList2(i)%nnodes = 4
    FaceList2(i)%nodes(1:4) = (/1,2,3,4/)
  end do
  print *, '3'
  read(*,*)

end program test

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

Крис
источник
1
Следует избегать «первого пути» в максимально возможной степени, поскольку он подвержен утечкам (массивы должны быть явно освобождены, как вы это сделали), кроме разницы в производительности, которую вы видите. Единственной причиной его использования было бы строгое соблюдение Fortran 95. Allocatable в производных типах были добавлены в TR 15581, но все компиляторы Fortran (даже те, которые не имеют каких-либо функций 2003) поддерживали их, то есть F95 + TR15581 + TR15580 с тех пор навсегда ,
Стали
1
Причиной этого является то, что некоторые лица могут иметь более 4 узлов.
Крис
Тогда это, безусловно, имеет смысл. Я предположил, что 4 было константой.
Стали

Ответы:

6

На самом деле я не знаю, как работают компиляторы фортрана, но, основываясь на особенностях языка, я могу предположить.

Динамические массивы в Фортране поставляются с метаданными для работы со встроенными функциями, такими как форма, размер, привязка, выделение и выделение или связывание (распределение по указателям). Для больших массивов размер метаданных незначителен, но для крошечных массивов, как в вашем случае, он может сложиться. В вашем случае динамические массивы размера 4, скорее всего, содержат больше метаданных, чем реальных данных, что приводит к увеличению объема используемой памяти.

Я настоятельно рекомендую против динамической памяти в нижней части ваших структур. Если вы пишете код, который имеет дело с физическими системами в некотором количестве измерений, вы можете установить его как макрос и перекомпилировать. Если вы имеете дело с графиками, вы можете статически выделить верхнюю границу для количества ребер или лайков. Если вы имеете дело с системой, которая действительно нуждается в детальном динамическом управлении памятью, то, вероятно, лучше всего перейти на C.

Макс Хатчинсон
источник
Да, но разве аргумент метаданных верен для обоих случаев?
Стали
@stali нет, обратите внимание, что во втором случае требуется один указатель, в отличие от nуказателей, необходимых для первого метода.
Арон Ахмадиа
Я добавил некоторую справочную информацию. Ваше предложение статически выделить верхнюю границу уже является хорошим улучшением. Верхняя граница - 8, но у большинства будет 4, только у небольшого процента будет 5,6,7 или 8. Таким образом, память все еще тратится впустую ...
Крис
@ Крис: Можете ли вы сделать два списка, один с 4, а другой с 8 узлами?
Педро
Наверное. Кажется, это хороший компромисс.
Крис
5

Как указал maxhutch , проблема, вероятно, заключается в большом количестве отдельных выделений памяти. Однако, помимо метаданных, вероятно, есть какие-то дополнительные данные и выравнивание, которые необходимы менеджеру памяти, то есть он, вероятно, округляет каждое выделение до некоторого кратного 64 байтам или более.

Чтобы избежать выделения небольшого фрагмента для каждого узла, вы можете попытаться выделить каждому узлу часть предварительно выделенного массива:

integer :: finger
indeger, dimension(8*n) :: theNodes

finger = 1
do i=1,n
    FaceList(i)%nodes => theNodes(finger:finger+FaceList(i)%nnodes-1)
    finger = finger + FaceList(i)%nnodes
end do

Мой Фортран немного ржавый, но вышеперечисленное должно работать, если не в принципе.

Вы по-прежнему будете иметь накладные расходы на то, что ваш компилятор Фортрана считает необходимым хранить для типа POINTER, но у вас не будет накладных расходов менеджера памяти.

Pedro
источник
это помогает, но только немного. Проблема в том, что это не один указатель, а динамический массив указателей: FaceList (i)% узлов (1: FaceList (i)% nnodes) => theNodes (finger: finger + FaceList (i)% nnodes-1). Это также подразумевает точную оценку размера предварительно выделенного массива.
Крис
@ Крис: Я не уверен, что полностью понимаю ... Что вы подразумеваете под "динамическим массивом указателей"? Поле nodesType%nodesявляется указателем на динамический массив.
Педро
0

Ой. Это та же проблема, что и у меня. Этот вопрос очень старый, но я предлагаю немного другой стиль кода. Моя проблема заключалась в размещаемом массиве операторов в производном типе данных, как следует из кода.

type :: node
  real*4,dimension(:),allocatable :: var4
  real*8,dimension(:),allocatable :: var8
end type node

type(node),dimension(:),allocatable :: nds

imax = 5000
allocate(nds(imax))

Из какого-то теста я подтвердил, что, если я использовал размещаемый оператор или оператор указателя в производном типе, как следует из кода, описывающего четыре случая, утечка памяти происходит очень сильно. В моем случае я красный файл размером 520 МБ. Но использование памяти в режиме релиза на Intel Fortran Complier составляло 4 ГБ. Это в 8 раз больше!

!(case 1) real*4,dimension(:),allocatable :: var4
!(case 2) real*4,dimension(:),pointer :: var4
!(case 3) real*4,allocatable :: var4(:,:)

!(case 4) 
type :: node(k)
  integer,len :: k = 4
  real*4 :: var4(k)
end type node

Утечка памяти не происходит, когда я использую выделение или указатель без производного типа. По моему мнению, если я объявляю переменную распределенного типа или указателя типа в производном типе, а большое выделяет переменную производного типа, а не переменную распределенного типа в производном типе, происходит утечка памяти. Чтобы решить эту проблему, я изменил свой код, который не включает производный тип, как следующий код.

real*4,dimension(:,:),allocatable :: var4 
! array index = (Num. of Nodes, Num. of Variables)

или как насчет этого стиля?

integer,dimension(:),allocatable :: NumNodes ! (:)=Num. of Cell or Face or etc.
integer,dimension(:),allocatable :: Node     ! (:)=(Sum(NumNodes))

Переменная NumNodes означает количество узлов на каждой грани, а переменная Node - это номера узлов, соответствующие переменной NumNodes. Возможно утечка памяти не произошла в этом стиле кода, я думаю.

G.Ku
источник