Використання пам'яті у fortran при використанні масиву похідного типу з покажчиком


13

У цій вибірковій програмі я роблю те саме (принаймні я так думаю) двома різними способами. Я запускаю це на своєму ПК на ПК та відстежую використання пам'яті зверху. Використовуючи 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. Виділені типи похідних типів були додані в TR 15581, але всі компілятори Fortran (навіть ті, які не мають жодних функцій 2003 року) підтримували їх, тобто F95 + TR15581 + TR15580 з назавжди .
stali

1
Причиною цього є те, що деякі обличчя можуть мати більше 4 вузлів.
chris

Тоді це, безумовно, має сенс. Я припускав, що 4 - константа.
stali

Відповіді:


6

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

Динамічні масиви у fortran поставляються з метаданими для роботи з внутрішніми функціями, такими як форма, розмір, фунт, ubound та виділені або асоційовані (розподіляються проти покажчиків). Для великих масивів розмір метаданих незначний, але для крихітних масивів, як у вашому випадку, вони можуть додаватися. У вашому випадку динамічні масиви розміром 4, ймовірно, мають більше метаданих, ніж реальних даних, що веде до використання вашої пам'яті.

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


Так, але хіба аргумент метаданих не відповідає дійсності для обох випадків?
stali

@stali ні, зауважте, що для другого випадку потрібен один покажчик, на відміну від nпокажчиків, необхідних для першого методу.
Арон Ахмадія

Я додав додаткову інформацію. Ваша пропозиція статично розподілити верхню межу вже є хорошим вдосконаленням. Верхня межа - 8, але більшість матиме 4, лише невеликий відсоток матиме 5,6,7 або 8. Тож пам'ять все-таки витрачається ...
chris

@chris: Чи можете ви скласти два списки, один з 4, а один з 8 вузлами?
Педро

Ймовірно. Здається, це хороший компроміс.
chris

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

Мій Fortran трохи іржавий, але вищезазначене повинно працювати, якщо не в принципі.

Ви все ще матимете накладні витрати того, що ваш компілятор Fortran вважає, що потрібно зберігати для типу POINTER, але у вас не буде накладних витрат менеджера пам'яті.


це допомагає, але лише трохи. Проблема полягає в тому, що це не один вказівник, а динамічний масив покажчиків: FaceList (i)% вузлів (1: FaceList (i)% nnodes) => theNodes (палець: палець + FaceList (i)% nnodes-1). Це також передбачає різку оцінку розміру попередньо виділеного масиву.
chris

@chris: Я не впевнений, що повністю розумію ... Що ви маєте на увазі під "динамічним масивом покажчиків"? Поле 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 Мб. Але використання пам'яті було 4 Гб в режимі випуску на Intel fortran complier. Це в 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. Можливо, витік пам'яті не відбувся в цьому стилі коду, я думаю.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.