Größe verschachtelter Arrays ermitteln

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Größe verschachtelter Arrays ermitteln

Beitrag von Timm Thaler »

Ich habe eine Struktur mit dynamischen Arrays, die dynamische Arrays enthalten, etwa dieser Art:

Code: Alles auswählen

  Tvalue = record
    name : string[20];
    un : string[10];
    wert : real;
  end;

  Tsensor = record
    kennung : string[20];
    time : TDateTime;
    value : array of Tvalue;
  end;

  Tdevice = record
    geraet : string[20];
    sensor : array of Tsensor;
  end;

  Tnode = record
    knoten : string[20];
    device : array of Tdevice;
  end; 
  
  var
    node : array of Tnode;
Ein Knoten kann also mehrere Geräte enthalten, diese mehrere Sensoren, diese mehrere Werte liefern. Oder auch keine.

Die Struktur wird aus einer XML eingelesen und dynamisch bei Programmstart aufgebaut. Da nicht bekannt ist, wieviele Geräte mit wievielen Sensoren, arbeite ich mit dynamischen Arrays, die entsprechend mit SetLength angepasst werden.

Das funktioniert soweit, aber: Ich würde jetzt gern sehen, wieviel Platz diese Struktur im Speicher einnimmt, zum Beispiel um die Stringlängen zu optimieren. Bekomme ich das irgendwie zur Laufzeit angezeigt, ohne jeden einzelnen Zweig aufzusummieren? Mit MemSize(@node) geht es anscheinend nicht.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6198
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Größe verschachtelter Arrays ermitteln

Beitrag von af0815 »

Mit Low() und high () auf jeden knoten kann man die Größe bestimmen.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Größe verschachtelter Arrays ermitteln

Beitrag von PascalDragon »

Timm Thaler hat geschrieben:
Sa 11. Jul 2020, 03:00
Das funktioniert soweit, aber: Ich würde jetzt gern sehen, wieviel Platz diese Struktur im Speicher einnimmt, zum Beispiel um die Stringlängen zu optimieren. Bekomme ich das irgendwie zur Laufzeit angezeigt, ohne jeden einzelnen Zweig aufzusummieren? Mit MemSize(@node) geht es anscheinend nicht.
Durch die dynamischen Arrays ist es im Speicher nicht eine Struktur. Dynamische Arrays sind Zeiger auf die Arraydaten. Es kann also sein, dass Tnode am einen Ende des virtuellen Speichers liegt und Tdevice am anderen (okay, so krass wird es nicht sein, da der Heap Manager von FPC das besser hinbekommt, aber theoretisch wäre es möglich).

Was genau möchtest du denn durch das Optimieren der Stringlängen erreichen?
FPC Compiler Entwickler

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 verschachtelter Arrays ermitteln

Beitrag von Socke »

Timm Thaler hat geschrieben:
Sa 11. Jul 2020, 03:00
Das funktioniert soweit, aber: Ich würde jetzt gern sehen, wieviel Platz diese Struktur im Speicher einnimmt, zum Beispiel um die Stringlängen zu optimieren.
Die Strings nehmen in deinem Beispiel immer 11 bzw. 21 Byte Speicher ein (10 bzw. 20 Zeichen + 1 Längenbyte) - egal wie viele Zeichen tatsächlich mit sinnvollem Inhalt belegt sind. Wenn du die Länge der Strings auf ein Minimum optimieren willst, musst du jeden einzelnen String prüfen.
Timm Thaler hat geschrieben:
Sa 11. Jul 2020, 03:00
Bekomme ich das irgendwie zur Laufzeit angezeigt, ohne jeden einzelnen Zweig aufzusummieren? Mit MemSize(@node) geht es anscheinend nicht.
Du könntest beim Heap-Manager nachfragen, wie viele Bytes vor und nach Aufbau der Struktur belegt sind. Das gibt aber nur ungefähre Werte.

Code: Alles auswählen

program Project1;

type
  rec = record
    s: String[20];
  end;
var
  a: array of rec;
  i: Integer;

begin
  writeln(1, '  ', GetFPCHeapStatus.CurrHeapUsed);
  for i := 1 to 100 do
  begin
    SetLength(a, i);
    writeln(i, '  ', GetFPCHeapStatus.CurrHeapUsed);
  end;
end.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Mathias
Beiträge: 6162
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Größe verschachtelter Arrays ermitteln

Beitrag von Mathias »

Die Grösse ermitteln, ist da etwa gleich kompliziert, wie wen man das ganze auf Platte speichern will. Forto Schleifen und mit Length ermitteln.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

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

Re: Größe verschachtelter Arrays ermitteln

Beitrag von wp_xyz »

Ich verstehe nicht, warum du da ShortStrings in die Records einbaust, wo doch gleichzeitig auch ein dynamisches Array variabler Länge vorhanden ist. ShortStrings machen Sinn, wenn du Records konstanter Größe haben willst, aber das ist wegen des dynamischen Arrays nicht der Fall. Und ShortStrings haben den Nachteil, dass du wissen musst, wie lang der längste String ist, damit nichts abgeschnitten wird und andererseits nicht zu viel Speicher verschenkt wird.

Um den Speicherbedarf aller Nodes zu ermitteln würde ich jeden Record mit einer Funktion Size:Int64 versehen, die für jedes Element aufgerufen wird:

Code: Alles auswählen

{$mode objfpc}
{$modeswitch advancedrecords}

const
  OVERHEAD = 2; // Pro String/dyn Array werden 2 zusätzliche Bytes für Länge und Refzähler belegt.

type
  Tvalue = record
    name : string; //[20];
    un : string; //[10];
    wert: real;
    function Size: Int64;
  end;

  Tsensor = record
    kennung : string; //[20];
    time : TDateTime;
    value : array of Tvalue;
    function Size: Int64;
  end;

  Tdevice = record
    geraet : string; //[20];
    sensor : array of Tsensor;
    function Size: Int64;
  end;

  Tnode = record
    knoten : string; //[20];
    device : array of Tdevice;
    function Size: Int64;
  end;

  function TValue.Size: Int64;
  begin
    Result := SizeOf(TValue) + Length(name) + Length(un) + 2* OVERHEAD;
  end;

  function TSensor.Size: Int64;
  var
    v: TValue;
  begin
    Result := SizeOf(TSensor) + Length(Kennung) + 2 * OVERHEAD;
    for v in value do
      Result := Result + v.Size;
  end;

  function TDevice.Size: Int64;
  var
    s: TSensor;
  begin
    Result := SizeOf(TDevice) + Length(Geraet) + 2 * OVERHEAD;
    for s in sensor do
      Result := Result + s.Size;
  end;

  function TNode.Size: Int64;
  var
    d: TDevice;
  begin
    Result := SizeOf(TNode) + Length(Knoten) + 2 * OVERHEAD;
    for d in device do
      Result := Result + d.Size;
  end; 

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 verschachtelter Arrays ermitteln

Beitrag von Socke »

wp_xyz hat geschrieben:
Sa 11. Jul 2020, 14:21
Ich verstehe nicht, warum du da ShortStrings in die Records einbaust, wo doch gleichzeitig auch ein dynamisches Array variabler Länge vorhanden ist. ShortStrings machen Sinn, wenn du Records konstanter Größe haben willst, aber das ist wegen des dynamischen Arrays nicht der Fall. Und ShortStrings haben den Nachteil, dass du wissen musst, wie lang der längste String ist, damit nichts abgeschnitten wird und andererseits nicht zu viel Speicher verschenkt wird.
Wenn bekannt ist, dass die Strings nicht länger als 10 bzw. 20 Zeichen sind, sind Shortstrings vollkommen in Ordnung. Bei einem Ansistring hast du einen viel höheren Speicheroverhead, den du erst einmal einsparen musst. Unter 32Bit-Systemen sind das 12 Byte pro String!
wp_xyz hat geschrieben:
Sa 11. Jul 2020, 14:21
Um den Speicherbedarf aller Nodes zu ermitteln würde ich jeden Record mit einer Funktion Size:Int64 versehen, die für jedes Element aufgerufen wird:
Ein sehr eleganter Ansatz! Nur der Overhead für dynamische Arrays und Ansistrings ist leider nicht nur 2 Bytes. Die internen Strukturen sind leider nicht direkt zugreifbar, sodass man sie selbst berechnen muss:

Code: Alles auswählen

const
  // tdynarray
  OVERHEAD_DYN_ARRAY = sizeof(ptrint) // refcount
                       +sizeof(tdynarrayindex); // high
  // TAnsiRec
  OVERHEAD_ANSISTRING = sizeof(TSystemCodePage) // CodePage
                       +sizeof(Word)            // ElementSize
{$ifdef CPU64}         +sizeof(DWord){$endif CPU64} // dummy, field alignment
                       +sizeof(SizeInt)         // Ref
                       +sizeof(SizeInt);        // Len    
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Warf
Beiträge: 1908
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Größe verschachtelter Arrays ermitteln

Beitrag von Warf »

Wenn die struktur einmal am anfang geladen wird, und du die größe optimieren willst, bau dir doch einfach nen stack allocator

Beispiel:

Code: Alles auswählen

program Project1;

{$mode objfpc}{$H+}

type

  { TStackAllocator }

  TStackAllocator = class
  private
    FCapacity: SizeInt;
    FSize: SizeInt;
    FData: PByte;

    procedure IncreaseCapacity;
  public
    function Allocate(const ElemSize: SizeInt; const Count: SizeInt): Pointer;
    constructor Create(const InitialCapacity: SizeInt);
    destructor Destroy; override;
  end;

{ TStackAllocator }

generic

procedure TStackAllocator.IncreaseCapacity;
begin
  FData:=ReAllocMem(FData, FCapacity * 2);
end;

function TStackAllocator.Allocate(const ElemSize: SizeInt; const Count: SizeInt
  ): Pointer;
begin
  while FSize + (ElemSize * Count) > FCapacity do
    IncreaseCapacity;
  Result := @FData[FSize];
  FSize += ElemSize * Count;
end;

constructor TStackAllocator.Create(const InitialCapacity: SizeInt);
begin
  FCapacity:=InitialCapacity;
  FSize:=0;
  FData:=GetMem(InitialCapacity);
end;

destructor TStackAllocator.Destroy;
begin
  Freemem(FData);
  inherited Destroy;
end;

type
PSomeRec = ^TSomeRec;
TSomeRec = record
  ID: Integer;
  ChildCount: SizeInt;
  Children: PSomeRec;
end;

var Stallocator: TStackAllocator;
  Root: PSomeRec;
  i: Integer;
begin
  Stallocator := TStackAllocator.Create(1024);
  root := Stallocator.Allocate(SizeOf(TSomeRec), 1);
  root^.ID := 0;
  root^.ChildCount := 5;
  root^.Children := Stallocator.Allocate(SizeOf(TSomeRec), root^.ChildCount);
  for i:=0 to Root^.ChildCount -1 do
  begin
    root^.Children[i].ChildCount := 0;
    root^.Children[i].Children := nil;
    root^.Children[i].ID := i + 1;
  end;
  Stallocator.Free;
end.  
Du bekommst die exakte größe byte genau über Stallocator.FSize

Antworten