Größe einer Struktur im Speicher bestimmen

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Größe einer Struktur im Speicher bestimmen

Beitrag von photor »

Hallo Forum,

ich glaube, ich habe ein Verständnisproblem. Ich will Ergebnisse aus FE-Rechnungen weiterverarbeiten. Dabei fallen enorme Mengen an Zahlen an, die ich strukturiert einlesen und verwalten muss. Ich beginne mit den Knoten, die eine Nummer und 2 oder 3 Raumkoordinaten besitzen. Also habe ich folgendes definiert:

Code: Alles auswählen

 
Type
  PNodeCoordinates = ^TNodeCoordinates;
  TNodeCoordinates = Record
    inod : integer;                 { user node number }
    xcord : Array[1..3] of double;  { coordinates of node }
  End;
 
  PAllNodalCoordinates = ^TAllNodalCoordinates;
  TAllNodalCoordinates = Record
    numnp  : integer;               { number of nodes }
    ncrd   : integer;               { number of coordinates per node }
    icoord : Array of TNodeCoordinates;   { array of node coordinates (numnp entries) }
  End;
 

Es gibt also eine Struktur, die die Nummer (als Integer) und ein Array für die 3 Koordinaten (Double) eines Knotens aufnehmen; diese Infos gehören zusammen. Dazu gibt es eine Struktur, die den gesamten Satz an Knoten beherbergen soll. Die hat die Info zu Gesamtzahl der Knoten (Integer), die Anzahl der Koordinaten pro Knoten (2 bzw. 3 für 2-D oder 3-D) und natürlich ein Array (dynamisch) mit der oben definierten Struktur für die Knoten selbst.

Das Einlesen passiert in einer Funktion, die wie folgt definiert ist:

Code: Alles auswählen

 
function ReadNodalCoordinates(postcode : TPostCodes; strlist : TStringList; idx : integer; numnodes : integer; coordpernode : integer) : PAllNodalCoordinates;
 


Die Struktur kann ich auch mit Daten füllen; zumindest liegen die Daten im Speicher und lassen sich auch auslesen (das habe ich mit dem Debugger geprüft - Code kann ich zeigen wenn gewünscht). Aber wenn ich versuche, den Speicherverbrauch festzustellen, bekomme ich absurd niedrige Werte:

Code: Alles auswählen

 
var
  PtrNodalCoordinates : PAllNodalCoordinates;
//...
  New(PtrNodalCoordinates);
  PtrNodalCoordinates := ReadNodalCoordinates(PCFileNo1,slT19File,idx,numNodes,.numCoordPerNode);
  Memo1.Append(' +---- Nodal Coordinates');
  Memo1.Append(' |     =================');
  Memo1.Append(' |  # Nodes:                '+IntToStr(PtrNodalCoordinates^.numnp));
  Memo1.Append(' |  # Coordinates per Node: '+IntToStr(PtrNodalCoordinates^.ncrd));
  Memo1.Append(' |  Length of coord[]:      '+IntToStr(Length(PtrNodalCoordinates^.icoord)));
  Memo1.Append(' |  coord['+IntToStr(PtrNodalCoordinates^.icoord[1000]. inod)+']:    ('+     // read node info of node number 1000 for test
                           FloatToStr(PtrNodalCoordinates^.icoord[1000].xcord[1])+'/'+
                           FloatToStr(PtrNodalCoordinates^.icoord[1000].xcord[2])+'/'+
                           FloatToStr(PtrNodalCoordinates^.icoord[1000].xcord[3])+')');
  Memo1.Append(' + ----------------------------- Memory: '+
                IntToStr(SizeOf(PtrNodalCoordinates^))+' Bytes');
 

ergibt folgendes im Memo1:

Code: Alles auswählen

 
+---- Nodal Coordinates
 |     =================
 |  # Nodes:                7695
 |  # Coordinates per Node: 3
 |  Length of coord[]:      7695
 |  coord[1001]:    (22.7388/10/0)
 + ----------------------------- Memory: 16 Bytes
 

Fragen:
  • die 16 Bytes sind sicher nicht die Gesamtlänge/größe der Struktur im Speicher sondern "misst" nur den Pointer auf das dynamische Array mit. Wie bekomme ich die Größe der ganzen Struktur? Die Anzahl der Knoten (= Länge des Arrays) stimmt genauso, wie die Nummer und die Koordinaten des Knotens. (Fußnote: wahrscheinlich ist die Antwort wieder so einfach, dass man sich anschließend wieder drei Tage nicht vor die Tür traut :| )
  • da das gleiche noch mit weiteren Strukturen geschehen soll (Elementdefinitionen, Ergebnisgrößen für einige Ergebnis-Files gleichzeitig), will ich sicher sein, dass ich den ganzen Mechanismus verstanden habe. Und ich möchte natürlich den Speicherverbrauch im Auge behalten bzw. im Voraus abschätzen können, wieviel ich brauche.
  • ist die Wahl eines dynamischen Arrays überhaupt die richtige Wahl? Oder wäre eine TList (bzw. TObjectList) besser geeignet? Geschwindigkeit ist neben dem Speicherverbrauch auch ein wichtiger Aspekt bei der Zahl an Daten.


Vielen Dank für Eure Hilfe,

Photor

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von wp_xyz »

Kennst du SizeOf()? Damit kannst du den Speicherbedarf einer Variablen bzw. eines Datentypen abfragen. Allerdings ist bei manchen Typen/Variablen verborgen, dass es sich eigentlich um Pointer handelt, so wie bei Strings und dynamischen Arrays. Hier musst du Length() mit der Größe eines Zeichens bzw. Array-Elements multiplizieren.

Code: Alles auswählen

function GetSizeOfAllNodalCoordinates(Value: TAllNodalCoordinates): Int64;
begin
  Result := SizeOf(TAllNodalCoordinates) + Length(Value.icoord) * SizeOf(TNodeCoordinates);
end;

Zur Frage, ob ein dynamisches Array die richtige Wahl ist, müsste man wissen, was du alles mit den Daten machen musst. Um ständig z.B. neue Daten unbekannter Anzahl anzufügen, ist ein dynamisches Array ungünstig, weil für die Erweiterung der Array-Dimension die bisherigen Daten immer umkopiert werden müssen. Auch zum Sortieren ist eine Liste günstiger, weil nur die Pointer vertauscht werden, nicht die gesamten Speicherbereiche. Listen haben dagegen den Nachteil, dass sie etwas mehr Aufwand bedeuten: du musst den Pointer immer in den Zeiger auf die Struktur casten (bzw. eine abgeleitete Listenklasse schreiben) - von generischen Listen würde ich die Finger lassen. Allein vom Zugriff auf ein Array- bzw. Listenelement her, denke ich, sollte kein großer Unterschied sein.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von Socke »

wp_xyz hat geschrieben:Kennst du SizeOf()? Damit kannst du den Speicherbedarf einer Variablen bzw. eines Datentypen abfragen. Allerdings ist bei manchen Typen/Variablen verborgen, dass es sich eigentlich um Pointer handelt, so wie bei Strings und dynamischen Arrays. Hier musst du Length() mit der Größe eines Zeichens bzw. Array-Elements multiplizieren.

Bei dynamischen Arrays und Strings werden immer noch einige Verwaltungsinformationen gespeichert wie z.B. Referenzzähler, Anzahl der Elemente, Größe eines Elements sowie bei Strings zusätzlich die CodePage.
Wenn man sehr, sehr viele kurze Arrays oder Strings verwendet, kann dies bei einer speicherarmen Umgebung problematisch werden; In den normalen Adressraum eines 32-Bit-Prozesses (2 GiB) pasen ca. 42 Millionen Strings (Verwaltungsdaten + 4 Byte für die String-Variable + 2 Byte Nutzdaten je String). Für die meisten Programme sollte das ausreichend sein, falls nicht, gibt es bereits 64-Bit-Prozessoren ;-)

Edit: wenn man nur die Verwaltungsdaten und 2 Byte Nutzdaten im Speicher ablegt (und keinen Pointer darauf), kann ein 32-Bit-Prozess unter Windows ca. 63 Millionen Strings anlegen. Da man auf die Strings dann aber nicht mehr zugreifen kann, ist dieses Beispiel ein wenig praxisfern.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von wp_xyz »

Ist natürlich richtig. Ich hatte mein Beispiel eher als Abschätzung verstanden (aber nicht hingeschrieben...), wobei ich auch im Hinterkopf hatte, dass es hier wahrscheinlich nicht um kurze Arrays geht.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von mse »

Socke hat geschrieben:Bei dynamischen Arrays und Strings werden immer noch einige Verwaltungsinformationen gespeichert wie z.B. Referenzzähler, Anzahl der Elemente, Größe eines Elements sowie bei Strings zusätzlich die CodePage.

Dazu kommt noch der Overhead im Memory-Manager für die Pointer-Datenblock-Verwaltung.

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von photor »

Hallo,

erstmal danke für Eure Antworten.

@ wp_xyz: die Funktion sizeof() hatte ich ja gerade verwendet (siehe Code-Schnipsel) und festgestellt, dass die mir nicht den wirklich verwendeten Speicherverbrauch liefert. Daher meine ganze "Verwirrung". Tenor Deines Beitrags - wenn richtig verstanden - ist, dass bei dynamischen Arrays ich tatsächlich mit Length() und den Typinformationen selber rechnen muss (so, wie Du angegeben hast und was ja auch kein Problem ist). Ich wollte ja auch nur sicher sein, dass die Daten richtig im Speicher liegen und auch da bleiben.

@Socke (und @mse): da es sich um Ergebnisse von FE-Rechnungen handelt, sind es sehr große bis riesige Arrays (btw. keine Strings sondern hauptsächlich doubles), so dass die paar Bytes Overhead den Braten nicht fett machen. Die Sache mit dem adressierbaren Speicher wird schon eher interessant: hier spiele ich mit Lazarus auf einem 64-bit Linux; somit sehe ich erstmal nur bedingt ein Problem (außer dem, dass mein Laptop bei 8 GB der RAM ausgeht). Das Programm, für das ich die Trockenübungen mache ist ein Delphi-Programm, dass momentan noch 32-bit-ig ist.

Aus den Antworten schließe ich auch, dass TLists nicht das unbedingte Mittel der Wahl ist.

Danke für's Feedback und ciao,

Photor

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von Socke »

wp_xyz hat geschrieben:Listen haben dagegen den Nachteil, dass sie etwas mehr Aufwand bedeuten: du musst den Pointer immer in den Zeiger auf die Struktur casten (bzw. eine abgeleitete Listenklasse schreiben) - von generischen Listen würde ich die Finger lassen. Allein vom Zugriff auf ein Array- bzw. Listenelement her, denke ich, sollte kein großer Unterschied sein.

Ich empfehle immer Listen, da der Zugriff darauf wesentlich verständlicher ist, als auf ein Array. Bei der Verwendung von TFPListe sollten auch keine großen Performance-Unterschiede zu erkennen sein.
TList ist etwas langsamer, da hier noch Eregnisse (Einfügen, Ändern, Löschen) eingebaut sind.
Mit generischen Liste (z.B. TFPGList aus der Unit fgl) habe ich bisher gute Erfahrungen in der Anwendung gemacht; sie sind nur schwierig zu debuggen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von mse »

Socke hat geschrieben:Bei der Verwendung von TFPListe sollten auch keine großen Performance-Unterschiede zu erkennen sein.

Es gibt schon Unterschiede.

Code: Alles auswählen

 
procedure tmainfo.listrunev(const sender: TObject);
var
 list: tfplist;
 t1: tdatetime;
 i1: int32;
 p1: pvaluesty;
 sum: double;
begin
 t1:= nowutc();
 list:= tfplist.create();
 try
  list.count:= counted.value;
  for i1:= 0 to list.count-1 do begin
   new(p1);
   p1^.a:= i1;
   p1^.b:= i1;
   list[i1]:= p1;
  end;
  sum:= 0;
  for i1:= 0 to list.count-1 do begin
   p1:= list[i1];
   sum:= sum + p1^.a + p1^.b
  end;
  for i1:= 0 to list.count-1 do begin
   dispose(pvaluesty(list[i1]));
  end;
 finally
  list.destroy();
 end;
 listdisp.value:= (nowutc()-t1)*24*60*60;
 listsumdi.value:= sum;
end;
 
procedure tmainfo.dynarrunev(const sender: TObject);
var
 ar1: valuesarty;
 sum: double;
 p1,pe: pvaluesty;
 t1: tdatetime;
 i1: int32;
begin
 t1:= nowutc();
 setlength(ar1,counted.value);
 p1:= pointer(ar1);
 pe:= p1+length(ar1);
 i1:= 0;
 while p1 < pe do begin
  p1^.a:= i1;
  p1^.b:= i1;
  inc(i1);
  inc(p1);
 end;
 sum:= 0;
 p1:= pointer(ar1);
 while p1 < pe do begin
  sum:= sum + p1^.a + p1^.b;
  inc(p1);
 end;
 ar1:= nil;
 dynardisp.value:= (nowutc()-t1)*24*60*60;
 dynarsumdi.value:= sum;
end;
 
Dateianhänge
list.png
list.png (9.41 KiB) 2126 mal betrachtet

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von Socke »

mse hat geschrieben:
Socke hat geschrieben:Bei der Verwendung von TFPListe sollten auch keine großen Performance-Unterschiede zu erkennen sein.

Es gibt schon Unterschiede.

Die sind insbesondere nachvollziehbar, da dein Quelltext ausufernd kommentiert und vollständig mit dem Free Pascal Compiler zu übersetzen ist.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von wp_xyz »

Ich weiß nicht, ob man daraus die allgemeine Aussage "ein dynamisches Array ist 4x schneller als TFPList" ableiten kann, denn hier wird nur einmal, nämlich zum Berechnen der Summe, auf die Array/Listen-Elemente zugegriffen, wodurch die Speicherallokierung und -freigabe übergewichtet wird. Die "Langsamkeit" von FPList rührt m.E. davon her, dass die Speicherallokierung viel komplizierter ist, denn während das Array nur einmal einen ausreichen großen Speicherbereich suchen muss, muss dies die Liste für jedes Element wiederholen. Genauso beim Aufräumen. Daher meine ich, dass das Beispiel die Verhältnisse gerade für die vom OP angesprochene FEM-Anwendung verzerrt, wo viele Lese-/Schreibzugriffe auf bereits vorhandene Element nötig sind. Und wenn erst eine Struktur unbekannter Größe aufgebaut werden muss, kommt das Array total ins Hintertreffen, da beim Einfügen neuer Elemente die bereits vorhanden Elemente neu kopiert werden müssen.

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von Socke »

wp_xyz hat geschrieben:Die "Langsamkeit" von FPList rührt m.E. davon her, dass die Speicherallokierung viel komplizierter ist, denn während das Array nur einmal einen ausreichen großen Speicherbereich suchen muss, muss dies die Liste für jedes Element wiederholen.

Bei korrekter Verwendung von Arrays und Listen besteht der Laufzeitunterschied ausschließlich im Methodenaufruf beim Zugriff. Die Speicherallokation kann komplett identisch am Anfang stattfinden (z.B. mit TFPList.Capacity. Gleiches trifft auf generischen Listen zu, die ganze Records beinhalten.

Meiner Meinung nach sollte man sich erst wieder auf die Ebene von Arrays begeben, wenn man mit Listen die Anwendung nicht schnell genug machen kann. Hier ist dann häufiger aber der Algorithmus und nicht die Liste schuld.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von wp_xyz »

Socke hat geschrieben:
wp_xyz hat geschrieben:Die "Langsamkeit" von FPList rührt m.E. davon her, dass die Speicherallokierung viel komplizierter ist, denn während das Array nur einmal einen ausreichen großen Speicherbereich suchen muss, muss dies die Liste für jedes Element wiederholen.

Die Speicherallokation kann komplett identisch am Anfang stattfinden (z.B. mit TFPList.Capacity.

Ich meinte eher das "New()" - das kann man bei der Listenlösung nicht zu Beginn erledigen.

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Größe einer Struktur im Speicher bestimmen

Beitrag von mse »

Hier mit integer statt double und 10000 Summierungsläufen:

Code: Alles auswählen

 
type
 valuesty = record
  a,b: int32;
 end;
 pvaluesty = ^valuesty;
 valuesarty = array of valuesty;
 
procedure tmainfo.listrunev(const sender: TObject);
var
 list: tfplist;
 t1: tdatetime;
 i1,i2: int32;
 p1: pvaluesty;
 sum: int32;
begin
 t1:= nowutc();
 list:= tfplist.create();
 try
  list.count:= counted.value;
  for i1:= 0 to list.count-1 do begin
   new(p1);
   p1^.a:= i1;
   p1^.b:= i1;
   list[i1]:= p1;
  end;
  for i2:= 0 to 9999 do begin
   sum:= 0;
   for i1:= 0 to list.count-1 do begin
    p1:= list[i1];
    sum:= sum + p1^.a + p1^.b
   end;
  end;
  for i1:= 0 to list.count-1 do begin
   dispose(pvaluesty(list[i1]));
  end;
 finally
  list.destroy();
 end;
 listdisp.value:= (nowutc()-t1)*24*60*60;
 listsumdi.value:= sum;
end;
 
procedure tmainfo.dynarrunev(const sender: TObject);
var
 ar1: valuesarty;
 sum: int32;
 p1,pe: pvaluesty;
 t1: tdatetime;
 i1,i2: int32;
begin
 t1:= nowutc();
 setlength(ar1,counted.value);
 p1:= pointer(ar1);
 pe:= p1+length(ar1);
 i1:= 0;
 while p1 < pe do begin
  p1^.a:= i1;
  p1^.b:= i1;
  inc(i1);
  inc(p1);
 end;
 for i2:= 0 to 9999 do begin
  sum:= 0;
  p1:= pointer(ar1);
  while p1 < pe do begin
   sum:= sum+p1^.a + p1^.b;
   inc(p1);
  end;
 end;
 ar1:= nil;
 dynardisp.value:= (nowutc()-t1)*24*60*60;
 dynarsumdi.value:= sum;
end;
 
Dateianhänge
list2.png

Antworten