Utilizzo della memoria in fortran quando si utilizza un array di tipo derivato con puntatore


13

In questo programma di esempio sto facendo la stessa cosa (almeno la penso così) in due modi diversi. Sto eseguendo questo sul mio PC Linux e monitorando l'utilizzo della memoria con top. Usando gfortran trovo che nel primo modo (tra "1" e "2") la memoria utilizzata sia di 8,2 GB, mentre nel secondo modo (tra "2" e "3") l'utilizzo della memoria è di 3,0 GB. Con il compilatore Intel la differenza è ancora maggiore: 10 GB contro 3 GB. Questo sembra una penalità eccessiva per l'utilizzo di puntatori. Perché succede?

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

Lo sfondo è il perfezionamento della griglia locale. Ho scelto l'elenco collegato per aggiungere e rimuovere facilmente i volti. Il numero di nodi è 4 per impostazione predefinita, ma può aumentare a seconda dei perfezionamenti locali.


1
Il "primo modo" dovrebbe essere evitato il più possibile poiché è soggetto a perdite (gli array devono essere esplicitamente deallocati, come è stato fatto) oltre alla differenza di prestazioni che si vede. L'unico motivo per usarlo sarebbe per la stretta aderenza a Fortran 95. Gli allocativi in ​​tipi derivati ​​sono stati aggiunti in TR 15581 ma tutti i compilatori Fortran (anche quelli che non hanno alcuna funzionalità del 2003) li hanno supportati, cioè F95 + TR15581 + TR15580 da sempre .
Stali,

1
Il motivo per farlo è che alcune facce potrebbero avere più di 4 nodi.
chris,

Quindi ha sicuramente senso. Ho pensato che 4 fosse una costante.
Stali,

Risposte:


6

In realtà non so come funzionano i compilatori fortran, ma in base alle funzionalità del linguaggio, posso immaginare.

Le matrici dinamiche in fortran sono dotate di metadati per lavorare con funzioni intrinseche come forma, dimensione, lbound, ubound e allocate o associate (allocabili vs puntatori). Per array di grandi dimensioni, la dimensione dei metadati è trascurabile, ma per array di piccole dimensioni, come nel tuo caso, può sommarsi. Nel tuo caso, gli array dinamici di dimensione 4 hanno probabilmente più metadati rispetto ai dati reali, il che sta portando al palloncino di utilizzo della memoria.

Consiglio vivamente contro la memoria dinamica nella parte inferiore delle tue strutture. Se stai scrivendo un codice che si occupa di sistemi fisici in un certo numero di dimensioni, puoi impostarlo come macro e ricompilare. Se hai a che fare con i grafici, puoi allocare staticamente un limite superiore sul numero di bordi o simili. Se hai a che fare con un sistema che richiede effettivamente un controllo dinamico della memoria a grana fine, probabilmente è meglio passare a C.


Sì, ma l'argomento dei metadati non vale per entrambi i casi?
Stali,

@stali no, nota che il secondo caso richiede un puntatore, al contrario dei npuntatori necessari al primo metodo.
Aron Ahmadia,

Ho aggiunto alcune informazioni di base. Il tuo suggerimento di allocare staticamente un limite superiore è già un buon miglioramento. Il limite superiore è 8, ma la maggioranza ne avrà 4, solo una piccola percentuale avrà 5,6,7 o 8. Quindi la memoria è ancora sprecata ...
chris

@chris: puoi creare due elenchi, uno con 4 e uno con 8 nodi?
Pedro

Probabilmente. Sembra essere un buon compromesso.
chris,

5

Come ha sottolineato maxhutch , il problema è probabilmente il semplice numero di allocazioni di memoria separate. Al di sopra dei metadati, tuttavia, è probabilmente presente qualsiasi dato aggiuntivo e allineamento di cui il gestore della memoria ha bisogno, ovvero probabilmente sta arrotondando ogni allocazione fino a un multiplo di almeno 64 byte.

Per evitare di allocare un piccolo blocco per ciascun nodo, è possibile provare ad allocare a ciascun nodo una porzione di un array pre-allocato:

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

Il mio Fortran è un po 'arrugginito, ma quanto sopra dovrebbe funzionare, se non in linea di principio.

Avresti ancora le spese generali di qualunque cosa il tuo compilatore Fortran pensi che debba archiviare per un tipo POINTER, ma non avrai le spese generali del gestore di memoria.


questo aiuta, ma solo un po '. Il problema è che non si tratta di un singolo puntatore ma di una matrice dinamica di puntatori: FaceList (i)% nodes (1: FaceList (i)% nnodes) => theNodes (finger: finger + FaceList (i)% nnodes-1). Implica anche una stima precisa della dimensione dell'array pre-allocato.
chris,

@chris: Non sono sicuro di aver capito completamente ... Cosa intendi con "matrice dinamica di puntatori"? Il campo nodesType%nodesè un puntatore a un array dinamico.
Pedro,

0

Oh. Questo è lo stesso problema che ho sofferto. Questa domanda è molto vecchia, ma suggerisco uno stile di codice leggermente diverso. Il mio problema era una matrice di istruzioni allocabile nel tipo di dati derivati, come segue codice.

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

Da alcuni test, ho confermato che se ho usato l'istruzione allocabile o l'istruzione pointer nel tipo derivato come codice follow su quattro casi, la perdita di memoria si verifica molto grande. Nel mio caso, ho rosso il file di dimensioni 520MB. Ma l'utilizzo della memoria era di 4 GB in modalità di rilascio su Intel Inttran Complier. Questo è 8 volte più grande!

!(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 perdita di memoria non si verifica quando utilizzo un'istruzione allocabile o puntatore senza tipo derivato. A mio avviso, se dichiaro la variabile di tipo allocabile o puntatore nel tipo derivato e grande allocare la variabile di tipo derivato non variabile allocabile nel tipo derivato, si verifica una perdita di memoria. Per risolvere questo problema, ho modificato il mio codice che non include il tipo derivato come segue codice.

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

o che ne dici di questo stile?

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

La variabile NumNodes indica il numero di nodi su ciascuna faccia e la variabile Node corrisponde ai numeri di nodo corrispondenti alla variabile NumNodes. Forse la perdita di memoria non si è verificata in questo stile di codice, credo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.