Utilisation de la mémoire dans fortran lors de l'utilisation d'un tableau de type dérivé avec pointeur

13

Dans cet exemple de programme, je fais la même chose (du moins je pense) de deux manières différentes. J'exécute cela sur mon PC Linux et surveille l'utilisation de la mémoire avec top. En utilisant gfortran, je trouve que dans la première manière (entre "1" et "2") la mémoire utilisée est de 8,2 Go, tandis que dans la seconde manière (entre "2" et "3") l'utilisation de la mémoire est de 3,0 Go. Avec le compilateur Intel, la différence est encore plus grande: 10 Go contre 3 Go. Cela semble une pénalité excessive pour l'utilisation de pointeurs. Pourquoi cela arrive-t-il?

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

L'arrière-plan est le raffinement du réseau local. J'ai choisi la liste chaînée pour ajouter et supprimer facilement des visages. Le nombre de nœuds est de 4 par défaut mais peut devenir plus élevé en fonction des raffinements locaux.

chris
la source
1
La "première façon" doit être évitée autant que possible car elle est sujette à des fuites (les tableaux doivent être explicitement désalloués, comme vous l'avez fait) en plus de la différence de performances que vous voyez. La seule raison de l'utiliser serait pour une stricte adhésion à Fortran 95. Les allocations dans les types dérivés ont été ajoutées dans TR 15581 mais tous les compilateurs Fortran (même ceux qui n'ont pas de fonctionnalités 2003) les ont pris en charge, à savoir F95 + TR15581 + TR15580 depuis toujours .
stali
1
La raison en est que certaines faces peuvent avoir plus de 4 nœuds.
chris
Alors cela a certainement du sens. J'ai supposé que 4 était une constante.
stali

Réponses:

6

Je ne sais pas vraiment comment fonctionnent les compilateurs fortran, mais en fonction des fonctionnalités du langage, je peux deviner.

Les tableaux dynamiques de fortran sont livrés avec des métadonnées pour fonctionner avec des fonctions intrinsèques telles que la forme, la taille, la valeur lbound, ubound et allouées ou associées (allouables vs pointeurs). Pour les grands tableaux, la taille des métadonnées est négligeable, mais pour les petits tableaux, comme dans votre cas, cela peut s'additionner. Dans votre cas, les tableaux dynamiques de taille 4 contiennent probablement plus de métadonnées que de données réelles, ce qui conduit à votre bulle d'utilisation de la mémoire.

Je déconseille fortement la mémoire dynamique au bas de vos structures. Si vous écrivez un code qui traite des systèmes physiques dans un certain nombre de dimensions, vous pouvez le définir comme macro et recompiler. Si vous traitez avec des graphiques, vous pouvez allouer statiquement une limite supérieure sur le nombre d'arêtes ou les goûts. Si vous avez affaire à un système qui a réellement besoin d'un contrôle de mémoire dynamique à grain fin, il est probablement préférable de passer en C.

Max Hutchinson
la source
Oui, mais l'argument des métadonnées n'est-il pas vrai dans les deux cas?
stali
@stali no, notez que le second cas nécessite un pointeur, contrairement aux npointeurs nécessaires à la première méthode.
Aron Ahmadia
J'ai ajouté quelques informations de fond. Votre suggestion d'allouer statiquement une limite supérieure est déjà une bonne amélioration. La limite supérieure est de 8, mais la majorité en aura 4, seul un petit pourcentage en aura 5,6,7 ou 8. Donc, la mémoire est toujours gaspillée ...
Chris
@chris: Pouvez-vous faire deux listes, une avec 4 et une avec 8 nœuds?
Pedro
Probablement. Cela semble être un bon compromis.
chris
5

Comme l' a souligné maxhutch , le problème est probablement le nombre important d'allocations de mémoire distinctes. En plus des métadonnées, cependant, il y a probablement toutes les données supplémentaires et l'alignement dont le gestionnaire de mémoire a besoin, c'est-à-dire qu'il arrondit probablement chaque allocation jusqu'à un multiple de 64 octets ou plus.

Pour éviter d'allouer un petit morceau pour chaque nœud, vous pouvez essayer d'allouer à chaque nœud une partie d'un tableau pré-alloué:

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

Mon Fortran est un peu rouillé, mais ce qui précède devrait fonctionner, sinon en principe.

Vous auriez toujours les frais généraux de tout ce que votre compilateur Fortran pense avoir besoin de stocker pour un type POINTER, mais vous n'aurez pas les frais généraux du gestionnaire de mémoire.

Pedro
la source
cela aide mais seulement un peu. Le problème est qu'il ne s'agit pas d'un seul pointeur mais d'un tableau dynamique de pointeurs: FaceList (i)% nœuds (1: FaceList (i)% nnodes) => theNodes (doigt: doigt + FaceList (i)% nnodes-1). Cela implique également une estimation précise de la taille du tableau pré-alloué.
chris
@chris: Je ne suis pas sûr de bien comprendre ... Qu'entendez-vous par "tableau dynamique de pointeurs"? Le champ nodesType%nodesest un pointeur vers un tableau dynamique.
Pedro
0

Oh. C'est le même problème que j'ai subi. Cette question est très ancienne, mais je suggère un style de code un peu différent. Mon problème était un tableau d'instructions pouvant être alloué dans un type de données dérivé, comme le code suivant.

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

À partir d'un test, j'ai confirmé que si j'utilisais l'instruction allocatable ou l'instruction pointeur dans le type dérivé comme code suivant dans quatre cas, les fuites de mémoire se produisent très grandes. Dans mon cas, j'ai rouge le fichier de 520Mo. Mais l'utilisation de la mémoire était de 4 Go en mode de sortie sur Intel Fortran complier. C'est 8 fois plus gros!

!(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

La fuite de mémoire ne se produit pas lorsque j'utilise une instruction allocatable ou pointer sans type dérivé. À mon avis, si je déclare la variable de type allouable ou pointeur dans le type dérivé et que grande alloue la variable de type dérivée variable non allouable dans le type dérivé, une fuite de mémoire se produit. Pour résoudre ce problème, j'ai changé mon code qui n'inclut pas le type dérivé comme code de suivi.

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

ou que diriez-vous de ce style?

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

La variable NumNodes signifie le nombre de nœuds sur chaque face et la variable Node est le nombre de nœuds correspondant à la variable NumNodes. Peut-être qu'une fuite de mémoire ne s'est pas produite dans ce style de code, je pense.

G.Ku
la source