Einzelner Memory Leak beim Zuweisen von Daten

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Hallo,

tauche gerade ein in die wunderwolle Welt des heaptrc. Nachdem ich all meinen Klassen Destruktoren verliehen habe, wird mir nur noch ein einziger Memory Leak angezeigt: 1 Unfreed memory blocks : 8. Diesen kann ich mir aber nicht erklären und hoffe auf eure Hilfe :)

Der Call-Trace führt mich zu diesen Code-Zeilen:

Code: Alles auswählen

procedure TContactListItem.assignData(data: PContactData);
begin
 
  FData := data;
 
  Refresh();      // Zeile 767, letzter aufgeführter call um Call-Trace, assignData wird zum Laden von Daten in das Item ausgeführt (ein einziges Mal)
 
end;
 
{ Betroffene types }
 
PPicture = ^TPicture;
 
TContactListItem = class(TPanel)
    private
      FData: PContactData;
      FMain, FSub: TLabel;
      FPic: TImage;
      FSelected: boolean;
 
      procedure assignData(data: PContactData);
 
    public
      constructor Create(cParent: TScrollBox; cData: PContactData);
      destructor Destroy(); override;
 
      procedure Refresh();
 
    property Data: PContactData read FData write assignData;
  end;
 
{ Methoden im Zusammenhang }
 
procedure TContactListItem.Refresh();
var picPointer: PPicture;
begin
 
  FMain.Caption := FData^.Name;
  FSub.Caption := FData^.Skill;
 
  if FData^.nation <> '' then
    FSub.Caption := FSub.Caption + '  ⋅  ' + FData^.Nation;
 
  New(picPointer);
  picPointer := picList.getPic(FData^.PicPath);
 
  if assigned(picPointer) then
    FPic.Picture.Assign(picPointer^);
 
  Dispose(picPointer);
 
end;
 
constructor TContactListItem.Create(cParent: TScrollBox; cData: PContactData);
begin
 
  inherited Create(cParent);
 
  Parent := cParent;
 
  FSelected := false;
 
  { create Layout }
 
    { create objects }
      FMain := TLabel.Create(Self);
      FMain.Parent := Self;
 
      FSub := TLabel.Create(Self);
      FSub.Parent := Self;
 
      FPic := TImage.Create(Self);
      FPic.Parent := Self;
 
    { Data Pointer }
      //New(FData);       // ursprünglich dachte ich, Speicherplatz für den Pointer reservieren zu müssen. beim Testen hat sich das aber als falsch erwiesen
 
  assignData(cData);
 
end;
 
destructor TContactListItem.Destroy();
begin
 
  FreeAndNil(FMain);
  FreeAndNil(FSub);
  FreeAndNil(FPic);
  //Dispose(FData);       // So wie ich ursprünglich Speicherplatz reservieren wollte, wollte ich auch ihn auch wieder frei geben. Das wurde mir mit einem SIGSEV-Fehler gedankt. Hab also New und Dispose auskommentiert
 
  inherited Destroy;
 
end;
 


Hat jemand eine Idee, was hier los sein könnte?

LG
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

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

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von Warf »

Speichere keine Pointer die du nicht selbst verwaltest. Wie schaut denn PContactData aus? was referenziert es? Das Problem ist das du ja nicht unbedingt weißt wie der speicher weiter verwaltet wird, darum kopiere lieber die Daten in eine selbstverwaltete struktur.
Z.B.

Code: Alles auswählen

 
 procedure TContactListItem.assignData(data: PContactData);
begin
 
  FData^ := data^;
 
  Refresh();     
 
end;
 
constructor TContactListItem.Create(cParent: TScrollBox; cData: PContactData);
begin
 
  inherited Create(cParent);
  ...
  New(FData);     
 
  assignData(cData);
 
end;
 
destructor TContactListItem.Destroy();
begin
  ...
  Dispose(FData);
  inherited Destroy;
end;


Außerdem, warum verwendest du überhaupt so viele Pointer, die Male in denen ich im letzen Jahr Pointer verwenden musste kann ich wahrscheinlich an einer Hand abzählen. Normalerweise kann man das ganze ganz gut umgehen

ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Vielen Dank für die Antwort! :)

Das Wichtigste zuerste: Dank dir ist der Fehler jetzt gelöst. Die Lösung war diese Zeile:

Code: Alles auswählen

FData^ := data^;

Mein Code lautete so:

Code: Alles auswählen

FData := data;

Allerdings kann ich noch nicht ganz nachvollziehen, warum meine Version nicht funktioniert. Das Disposen eines Pointers, wohlgemerkt. Beim Create und zur Laufzeit wurden die Daten korrekt angezeigt, nur beim Freen des ListItems kam bei Dispose eine Fehlermeldung

Der Memory-Leak tritt aber leider immer noch auf.

Merkwürdig ist ja, dass der ganze Pointer-Spaß auch funktioniert hat, wenn ich den Pointer NICHT via New() im Create erzeugt habe (siehe Code im ersten Post)

Zu deinen Fragen:
Bei dem Programm handelt es sich um eine Kontakt-Datenbank. Die Ergebnisse einer Abfrage (Kontakte, die zu Suchkriterien passen) werden aus einer SQL3-DB geladen und als record-Array global gespeichert. Im Ergebnis enthaltene Bilder werden beim ersten Aufruf in einer globalen Liste gespeichert und im folgenden Verlauf nur noch aus dieser geladen. Jede Komponente verlinkt sich bei Bedarf via Pointern mit den nötigen Daten.
Durch diesen ganzen Aufwand erhoffe ich mir Geschwindigkeitsvorteile und weniger benötigten Arbeitsspeicher, da die Ergebnisse der Suche in Echtzeit (beim Tippen) angezeigt werden sollen. Zumindest für die Bilder-Liste trifft das auch zweifelsfrei zu, bei den restlichen Daten bin ich mir unsicher.

PContactData ist ein Zeiger auf einen Kontakt-Datensatz:

Code: Alles auswählen

TContactData = record
      Name, Skill, City, PicPath: string[255];
      Nation: string[50];
      Phone: string[30];
      AddConInfo, Comments: string;
      EMail: string[60];
      Event: TEventData;
      Rating: byte;
      BDay: TDate;
      Created: TDateTime;
      ID: integer;
    end;
 
PContactData = ^TContactData;
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

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

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von Warf »

ConcAPPtLab hat geschrieben:Vielen Dank für die Antwort! :)

Das Wichtigste zuerste: Dank dir ist der Fehler jetzt gelöst. Die Lösung war diese Zeile:

Code: Alles auswählen

FData^ := data^;

Mein Code lautete so:

Code: Alles auswählen

FData := data;

Allerdings kann ich noch nicht ganz nachvollziehen, warum meine Version nicht funktioniert. Das Disposen eines Pointers, wohlgemerkt. Beim Create und zur Laufzeit wurden die Daten korrekt angezeigt, nur beim Freen des ListItems kam bei Dispose eine Fehlermeldung

Der Memory-Leak tritt aber leider immer noch auf.

Merkwürdig ist ja, dass der ganze Pointer-Spaß auch funktioniert hat, wenn ich den Pointer NICHT via New() im Create erzeugt habe (siehe Code im ersten Post)

Zu deinen Fragen:
Bei dem Programm handelt es sich um eine Kontakt-Datenbank. Die Ergebnisse einer Abfrage (Kontakte, die zu Suchkriterien passen) werden aus einer SQL3-DB geladen und als record-Array global gespeichert. Im Ergebnis enthaltene Bilder werden beim ersten Aufruf in einer globalen Liste gespeichert und im folgenden Verlauf nur noch aus dieser geladen. Jede Komponente verlinkt sich bei Bedarf via Pointern mit den nötigen Daten.
Durch diesen ganzen Aufwand erhoffe ich mir Geschwindigkeitsvorteile und weniger benötigten Arbeitsspeicher, da die Ergebnisse der Suche in Echtzeit (beim Tippen) angezeigt werden sollen. Zumindest für die Bilder-Liste trifft das auch zweifelsfrei zu, bei den restlichen Daten bin ich mir unsicher.

PContactData ist ein Zeiger auf einen Kontakt-Datensatz:

Code: Alles auswählen

TContactData = record
      Name, Skill, City, PicPath: string[255];
      Nation: string[50];
      Phone: string[30];
      AddConInfo, Comments: string;
      EMail: string[60];
      Event: TEventData;
      Rating: byte;
      BDay: TDate;
      Created: TDateTime;
      ID: integer;
    end;
 
PContactData = ^TContactData;


Mach dir keine sorgen um die performance. Solang du nicht unbedingt millionen von kontakten hast wirst du keine probleme haben, und wenn du millionen von kontakten hast solltest du eh ein Datenbanksystem verwenden (immerhin steckt in denen Jahre der optimierung drin). Ich würde schätzen das du auf einem normalen Computer (2+ GHz) problemlos ein paar tausende kontakte managen kannst ohne merklicher performanceverluste, auch ohne Pointer.

Viel wichtiger sind da intelligente Datenstrukturen und algorithmen. Z.B. auf einer sortierten Liste kannst du mit Binärsuche in Logarithmischer Zeit suchen, in einem RBT sind alle operationen logarithmisch mit geringem konstanten overhead. Für Stringsuchen hab ich mir mal einen Präfixtree geschrieben, ein Tree in der jede Node einen char darstellt, und jeder Pfad im baum ein Wort ist, sodass über einen Pointer in der endnode der ein Objekt referenziert werden kann. (damit ist das suchen nach strings in konstanter zeit möglich).

Und denk dran speicher ist weniger wertvoll als performance. Daher mach zum performance gewinn redundanzen rein wie zusätzliche pointer/sortierte listen

ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Okay, danke für die Tips :)
Bei den Pointern zu den einfach Datentypen wirst du sicher Recht haben - aber die Bilderliste hat einen extremen Vorteil gebracht, dabei habe ich zum Testen aktuell nur um die 10 Kontakte.

Was die Suche angeht lehne ich mich einfach zurück und lass SQLite3 die Arbeit machen.

Hast du eine Idee, was das Speicherleck verursachen könnte? Es tritt irgendwie an dieser Stelle auf
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

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

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von Warf »

ConcAPPtLab hat geschrieben:Okay, danke für die Tips :)
Bei den Pointern zu den einfach Datentypen wirst du sicher Recht haben - aber die Bilderliste hat einen extremen Vorteil gebracht, dabei habe ich zum Testen aktuell nur um die 10 Kontakte.

Was die Suche angeht lehne ich mich einfach zurück und lass SQLite3 die Arbeit machen.

Hast du eine Idee, was das Speicherleck verursachen könnte? Es tritt irgendwie an dieser Stelle auf


Naja erstmal brauchst du Komponenten nicht zu freeen wenn du Self beim constructor übergibst.
Für das Memory leak poste am besten mal den gesamten heaptrc Stack, so kann ich da nix erkennen

ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Das hier ist der Heaptrace nach Programmstart und sofortigem Schließen. Zwischendurch wurden 13 TContactListItems erzeugt und mit Daten gefüllt/gelinkt.

Code: Alles auswählen

C:\Users\Mein_Toller_Pfad_Zum_Programm\Dialry v1.0.1-0\Dialry.exe 
Heap dump by heaptrc unit
19622 memory blocks allocated : 120935096/120956416
19609 memory blocks freed     : 120934992/120956312
13 unfreed memory blocks : 104
True heap size : 884736
True free heap : 882464
Should be : 882968
Call trace for block $00000000000C7920 size 8
  $0000000100030E58 line 999 of unit1.pas      // siehe Code unten
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7880 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C77E0 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7740 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C76A0 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7600 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7560 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C74C0 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7420 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7380 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C72E0 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C7240 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
Call trace for block $00000000000C6D40 size 8
  $0000000100030E58 line 999 of unit1.pas
  $0000000100030C56 line 971 of unit1.pas
  $0000000100031876 line 1175 of unit1.pas
  $0000000100031956 line 1194 of unit1.pas
  $000000010002F838 line 516 of unit1.pas
  $0000000100020AD4 line 1007 of include/customform.inc
  $000000010001FEC0 line 630 of include/customform.inc
  $BAADF00DBAADF00D
 
procedure TContactListItem.assignData(data: PContactData);
begin
 
  FData^ := data^;
 
  Refresh();     // Zeile 999
 
end;
 
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

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

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von Warf »

Mach das new und dispose bei refresh weg oder übergib nicht den Pointer sondern den Inhalt in picPointer := picList.getPic(FData^.PicPath);

ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Danke noch einmal. Habe jetzt auf den lokalen Pointer verzichtet. New/Dispose in Refresh war der Grund.

Mein Code:

Code: Alles auswählen

procedure TContactListItem.Refresh();
var picPointer: TPicture;
begin
 
  FMain.Caption := FData^.Name;
  FSub.Caption := FData^.Skill;
 
  if FData^.nation <> '' then
    FSub.Caption := FSub.Caption + '  ⋅  ' + FData^.Nation;
 
  picPointer := picList.getPic(FData^.PicPath);
 
  if assigned(picPointer) then
    FPic.Picture.Assign(picPointer);
 
end;


Aber warum? Wozu rufe ich denn Dispose auf, wenn der Pointer sowieso nicht freigegeben wird?

EDIT
Okay...anscheinend wird der Pointer automatisch beim Funktionsaufruf erzeugt und am Ende wieder freigegeben. Jedenfalls funktioniert das alles ohne Memory-Leak, wenn ich New und Dispose einfach weg lasse...

Welche Variante ist aus performance-technischen Gründen besser: Die Übergabe eines Pointers PPointer auf ein Bild oder die Übergabe von TPicture (siehe Code)?
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

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

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von Warf »

Nein, es liegt daran das Piclist dir einen Pointer zurückgibt, du damit deinen alten pointer überschreibst und der ist weg. Durch das dispose gibst du dann den Pointer von Piclist frei und nicht deinen vorher erstellent.

ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Aha! Trotzdem verstehe ich nicht, wieso ich picPointer gar nicht mehr mit New() reservieren muss

Wenn die Performance nicht drunte rleidet, würde ich den ganzen Pointer-Quatsch ja lassen, aber ich bin mir da halt nicht sicher ^^
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

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

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von Warf »

Weil du den pointer von piclist bekommst, also muss piclist sich um die verwaltung kümmern nicht du

ConcAPPtLab
Beiträge: 89
Registriert: Fr 18. Apr 2014, 18:57

Re: Einzelner Memory Leak beim Zuweisen von Daten

Beitrag von ConcAPPtLab »

Okay, danke für deine Hilfe :)
Definition "Strategische Fehlerkorrektur":
Solange rumprobieren bisses klappt :D

Antworten