Speichernutzung in fortran bei Verwendung eines Arrays eines abgeleiteten Typs mit Zeiger

13

In diesem Beispielprogramm mache ich dasselbe (zumindest denke ich das) auf zwei verschiedene Arten. Ich führe dies auf meinem Linux-PC aus und überwache die Speichernutzung mit top. Bei der Verwendung von gfortran stelle ich fest, dass auf die erste Weise (zwischen "1" und "2") der verwendete Speicher 8,2 GB beträgt, während auf die zweite Weise (zwischen "2" und "3") die Speichernutzung 3,0 GB beträgt. Beim Intel-Compiler ist der Unterschied sogar noch größer: 10 GB gegenüber 3 GB. Dies scheint eine übermäßige Strafe für die Verwendung von Zeigern zu sein. Warum passiert das?

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

Hintergrund ist die lokale Netzverfeinerung. Ich habe die verknüpfte Liste ausgewählt, um Gesichter einfach hinzuzufügen und zu entfernen. Die Anzahl der Knoten ist standardmäßig 4, kann jedoch abhängig von den lokalen Verfeinerungen höher werden.

chris
quelle
1
Der "erste Weg" sollte so weit wie möglich vermieden werden, da er zu Undichtigkeiten neigt (Arrays müssen wie Sie explizit freigegeben werden). Der einzige Grund, es zu verwenden, wäre die strikte Einhaltung von Fortran 95. Allocatable's in abgeleiteten Typen wurden in TR 15581 hinzugefügt, aber alle Fortran-Compiler (auch diejenigen, die keine 2003-Funktionen haben) haben sie unterstützt, dh F95 + TR15581 + TR15580 seit Ewigkeiten .
Stali
1
Der Grund dafür ist, dass einige Flächen möglicherweise mehr als 4 Knoten haben.
Chris
Dann macht es sicher Sinn. Ich nahm an, dass 4 eine Konstante war.
Stali

Antworten:

6

Eigentlich weiß ich nicht, wie Fortran-Compiler funktionieren, aber ich kann anhand der Sprachfunktionen raten.

Dynamische Arrays in fortran werden mit Metadaten geliefert, um mit Funktionen wie Form, Größe, Lbound, Ubound und Zugewiesen oder Zugeordnet (zuweisbar gegen Zeiger) zu arbeiten. Bei großen Arrays ist die Größe der Metadaten vernachlässigbar, bei kleinen Arrays kann sie sich jedoch wie in Ihrem Fall summieren. In Ihrem Fall haben die dynamischen Arrays der Größe 4 wahrscheinlich mehr Metadaten als echte Daten, was zu Ihrer Speicherauslastung führt.

Ich würde dringend gegen dynamische Speicher am unteren Rand Ihrer Strukturen empfehlen. Wenn Sie einen Code schreiben, der sich mit physischen Systemen in einer Reihe von Dimensionen befasst, können Sie ihn als Makro festlegen und neu kompilieren. Wenn Sie sich mit Diagrammen beschäftigen, können Sie statisch eine Obergrenze für die Anzahl der Kanten oder dergleichen zuweisen. Wenn Sie es mit einem System zu tun haben, das tatsächlich eine feinkörnige dynamische Speichersteuerung benötigt, ist es wahrscheinlich am besten, auf C zu wechseln.

Max Hutchinson
quelle
Ja, aber gilt das Metadaten-Argument nicht für beide Fälle?
Stali
@stali nein, beachte, dass der zweite Fall einen Zeiger erfordert, im Gegensatz zu den nZeigern, die von der ersten Methode benötigt werden.
Aron Ahmadia
Ich habe einige Hintergrundinformationen hinzugefügt. Ihr Vorschlag, eine Obergrenze statisch zuzuweisen, ist bereits eine gute Verbesserung. Die obere Grenze ist 8, aber die Mehrheit wird 4 haben, nur ein kleiner Prozentsatz wird 5,6,7 oder 8 haben. Also wird noch Speicher verschwendet ...
Chris
@chris: Kannst du zwei Listen erstellen, eine mit 4 und eine mit 8 Knoten?
Pedro
Wahrscheinlich. Es scheint ein guter Kompromiss zu sein.
Chris
5

Wie Maxhutch herausgestellt hat, liegt das Problem wahrscheinlich in der bloßen Anzahl separater Speicherzuordnungen. Zusätzlich zu den Metadaten gibt es wahrscheinlich alle zusätzlichen Daten und Ausrichtungen, die der Speichermanager benötigt, dh, es wird wahrscheinlich jede Zuordnung auf ein Vielfaches von 64 Bytes oder mehr aufgerundet.

Um zu vermeiden, dass jedem Knoten ein kleiner Block zugewiesen wird , können Sie versuchen, jedem Knoten einen Teil eines zuvor zugewiesenen Arrays zuzuweisen:

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

Mein Fortran ist ein bisschen rostig, aber das obige sollte funktionieren, wenn nicht im Prinzip.

Sie haben immer noch den Overhead von allem, was Ihr Fortran-Compiler für einen POINTER-Typ benötigt, aber Sie haben nicht den Overhead für den Speichermanager.

Pedro
quelle
das hilft aber nur wenig. Das Problem ist, dass es sich nicht um einen einzelnen Zeiger handelt, sondern um ein dynamisches Array von Zeigern: FaceList (i)% node (1: FaceList (i)% nnodes) => theNodes (finger: finger + FaceList (i)% nnodes-1). Dies impliziert auch eine genaue Schätzung der Größe des vorab zugewiesenen Arrays.
Chris
@chris: Ich bin nicht sicher, ob ich das vollständig verstehe ... Was meinst du mit einem "dynamischen Array von Zeigern"? Das Feld nodesType%nodesist ein Zeiger auf ein dynamisches Array.
Pedro
0

Oh. Das ist das gleiche Problem, unter dem ich gelitten habe. Diese Frage ist sehr alt, aber ich schlage einen etwas anderen Codestil vor. Mein Problem war zuweisbares Anweisungsarray im abgeleiteten Datentyp, wie folgt Code.

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

Bei einigen Tests habe ich bestätigt, dass ein Speicherverlust sehr groß ist, wenn ich die zuweisbare Anweisung oder die Zeigeranweisung im abgeleiteten Typ wie im folgenden Code für vier Fälle verwendet habe. In meinem Fall habe ich die Datei mit einer Größe von 520 MB rot. Die Speicherauslastung betrug jedoch 4 GB im Release-Modus auf Intel Fortran-kompatiblen Rechnern. Das ist 8 mal größer!

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

Ein Speicherverlust tritt nicht auf, wenn ich eine zuweisbare Anweisung oder eine Zeigeranweisung ohne abgeleiteten Typ verwende. Wenn ich meiner Meinung nach die Variable des zuweisbaren Typs oder des Zeigertyps als abgeleiteten Typ deklariere und die Variable des abgeleiteten Typs als nicht zuweisbare Variable im abgeleiteten Typ groß reserviere, tritt ein Speicherleck auf. Um dieses Problem zu lösen, habe ich meinen Code, der keinen abgeleiteten Typ enthält, wie folgt geändert.

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

oder wie wäre es mit diesem Stil?

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

NumNodes-Variable bedeutet die Anzahl der Knoten auf jeder Fläche und Node-Variable die Knotennummern, die mit NumNodes-Variablen übereinstimmen. Vielleicht ist in diesem Codestil kein Speicherverlust aufgetreten, denke ich.

G. Ku
quelle