AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Memory-Leck? Verkettete Records brauchen extrem viel Ram...
Thema durchsuchen
Ansicht
Themen-Optionen

Memory-Leck? Verkettete Records brauchen extrem viel Ram...

Ein Thema von Eskayp · begonnen am 14. Apr 2005 · letzter Beitrag vom 15. Apr 2005
Antwort Antwort
Seite 1 von 2  1 2      
Eskayp

Registriert seit: 14. Apr 2005
5 Beiträge
 
#1

Memory-Leck? Verkettete Records brauchen extrem viel Ram...

  Alt 14. Apr 2005, 16:16
Hallo Leute,

ich würde mich nicht als Delphi-Noob bezeichnen, aber auch noch lange nicht als Profi.

Eine von mir programmierte Unit soll ein verschlüsseltes Datenbankfile einlesen und daraus Daten filtern und zurückgeben können. Das ganze soll später mal in eine DLL verpackt werden, daher arbeite ich mit ShortStrings.

Das Datenbankfile hat verschlüsselt (nicht komprimiert) gerade mal 260 KB. Wenn ich dies in meiner Anwendung nur einlese und in den verketteten Records speichere, braucht meine Anwendung erstens mehrere Sekunden und belegt dann zweitens 21 MB im Ram (ohne geladene Daten nur 2.5 MB).

Also irgendwo kann dann da doch was nicht stimmen, oder? Bin für jede Hilfe dankbar.

Ach, fragt bitte nicht, warum ich nicht ne fertige DB-Schnittstelle nutze hat so seine Gründe...

Der Übersichtlichkeit halber nur Codeausschnitte...

Delphi-Quellcode:
unit CodeTestDLL;

interface

uses
  SysUtils,
  Dialogs,
  Classes;

type
  PTmyDBRow = ^TmyDBRow;
  TmyDBRow = record
    value: Array of ShortString;
    marker: Boolean;
    next: PTmyDBRow;
  end;

  PTmyDBField = ^TmyDBField;
  TmyDBField = record
    name: ShortString;
    next: PTmyDBField;
  end;

  PTmyDB = ^TmyDB;
  TmyDB = record
    name: ShortString;
    fieldcnt: Integer;
    fields: PTmyDBField;
    data: PTmyDBRow;
    next: PTmyDB;
  end;

  PTmyTmp = ^TmyTmp;
  TmyTmp = record
    value: ShortString;
    next: PTmyTmp;
  end;

  procedure MyDBDecode(var pLine: ShortString);
  procedure MyDBExplode(pHaystack: String);
  function MyDBFieldNr(FieldName: ShortString; var p: Integer): Boolean;
  function MyDBGetError(): ShortString; stdcall;
  function MyDBLoad(DBName, FileName: ShortString): Boolean;stdcall;
  procedure MyDBSelect(DBName, DBFields: ShortString); stdcall;
  procedure MyDBFilter(FieldName, Value: ShortString); stdcall;
  function MyDBGetResult(var Values: ShortString): Boolean; stdcall;


var
  myDBs: PTmyDB;
  myLastDB: ShortString;
  myLastFields: ShortString;
  myResult: PTmyDBRow;
  myTmpVal: PTmyTmp;
  myError: ShortString;

  curDB: PTmyDB;

const
  mySepStr : Char = '|';
  myErrOpenFailed : ShortString = 'Öffnen der Datenbank fehlgeschlagen';
  myErrWrongHeader: ShortString = 'Format der Datenbank unbekannt';
  myErrCorrupt : ShortString = 'Datenbank korrupt';
  myFileHeader : ShortString = 'waspdbfile_v1.0';

implementation

// Da die Datenbankdateien verschlüssel sind, muss jede einzelne Zeile mit Hilfe
// dieser Routine entschlüsselt werden. Ist nicht gerade extrem sicher, aber
// reicht gegen "normale Hacker" ;)
procedure MyDBDecode(var pLine: ShortString);
  // [...]
end;

// Das Aufsplitten einer Zeile anhand des SepStrings übernimmt diese Routine.
// Das Ergebnis wird in einem verketteten, temporärem Record gespeichert. So
// kann es dann später leicht in verschiedene Datenformen umgewandelt werden.
procedure MyDBExplode(pHaystack: String);
var
  i: Integer;
  sValue: String;
  TmpVal: PTmyTmp;
begin
  TmpVal := nil;

  // Erstmal das temporäre Feld löschen, falls noch voll
  while (myTmpVal <> nil) do begin
    TmpVal := myTmpVal;
    myTmpVal := myTmpVal^.next;
    Dispose(TmpVal);
  end;

  // Dann Stück für Stück die Zeile durchgehen und bei Auffinden des SplitChr
  // den so gesammelten Wert in das verkettete Record setzen.
  sValue := '';
  for i := 1 to Length(pHaystack) do begin
    if (pHaystack[i] = mySepStr) then begin
      // Zwischenwert im verketteten Record speichern
      if (myTmpVal = nil) then begin
        New(myTmpVal);
        TmpVal := myTmpVal;
      end
      else begin
        New(TmpVal^.next);
        TmpVal := TmpVal^.next;
      end;
      TmpVal^.value := sValue;
      TmpVal^.next := nil;

      sValue := ''; // Zwischenspeicher zurücksetzen
    end
    else begin
      sValue := sValue + pHaystack[i];
    end;
  end;
end;

// Jedes Feld bekommt ja eine Nummer. Diese Routine ermittelt die zugehörige
// Nummer eines Feldes (=Spalte in Tabelle).
function MyDBFieldNr(FieldName: ShortString; var p: Integer): Boolean;
var
  curField: PTmyDBField;
begin
  // An welcher Stelle steht denn Fieldname?
  curField := curDB^.fields;
  p := 0;
  while (curField <> nil) and (curField^.name <> FieldName) do begin
    curField := curField^.next;
    Inc(p);
  end;
  Result := (curField <> nil);
end;

// Diese Routine soll zu beginn der Anwendung erst einmal alle verfügbaren
// Datenbankdateien einlesen und in mehreren verketteten Records speichern.
// Die Routine sucht die Datenbanken nicht selber, sondern bekommt jede einzeln
// per Parameter (wird also pro DB einmal aufgerufen).
function MyDBLoad(DBName, FileName: ShortString): Boolean; stdcall;
var
  DBFile: TextFile;
  FileOpened: Boolean;
  Line: ShortString;
  TmpVal: PTmyTmp;
  curField: PTmyDBField;
  curRow: PTmyDBRow;
  cnt, n: Integer;
begin
  myError := '';
  Result := false;
  FileOpened := false;

  curField := nil;
  curRow := nil;
  cnt := 0;

  // Datei öffnen
  AssignFile(DBFile, FileName);
  {$I-}
  FileMode := 0;
  Reset(DBFile);

  // Dateiheader lesen
  if (IOResult = 0) then begin
    FileOpened := True;
    Readln(DBFile, Line);
  end
  else myError := myErrOpenFailed;

  if (myError = '') then begin
    if (IOResult <> 0) then myError := myErrOpenFailed;
  end;

  // Dateispalten einlesen ...
  if (myError = '') then begin
    if (Line = myFileHeader) then begin
      Readln(DBFile, Line);
    end
    else myError := myErrWrongHeader;
  end;

  // ... und merken
  if (myError = '') then begin
    if (IOResult = 0) then begin
      // Jetzt kommen ja definitiv Daten, also muss die neue Datenbank
      // angelegt werden
      if (myDBs = nil) then begin
        New(myDBs);
        curDB := myDBs;
      end
      else begin
        curDB := myDBs;
        while (curDB^.next <> nil) do
          curDB := curDB^.next;
        New(curDB^.next);
        curDB := curDB^.next;
      end;
      curDB^.name := DBName;
      curDB^.fields := nil;
      curDB^.data := nil;
      curDB^.next := nil;

      // Daten entschlüsseln und splitten
      MyDBDecode(Line);
      MyDBExplode(Line);

      // Daten im Field-Record speichern und Felder (=Spalten) zählen
      cnt := 0;
      TmpVal := myTmpVal;
      while (TmpVal <> nil) do begin
        if (curDB^.fields = nil) then begin
          New(curDB^.fields);
          curField := curDB^.fields;
        end
        else begin
          New(curField^.next);
          curField := curField^.next;
        end;
        curField^.name := TmpVal^.value;
        curField^.next := nil;

        TmpVal := TmpVal^.next;
        Inc(cnt);
      end;

      curDB^.fieldcnt := cnt;
    end
    else myError := myErrCorrupt;
  end;

  // Daten einlesen und im Array ablegen
  if (myError = '') then begin
    while not Eof(DBFile) do begin
      Readln(DBFile, Line);
      if (IOResult = 0) and (Line <> '') then begin
        // Daten entschlüsseln und splitten
        MyDBDecode(Line);
        MyDBExplode(Line);
        // Neue Zeile (Datensatz, Row) erzeugen
        if (curDB^.data = nil) then begin
          New(curDB^.data);
          curRow := curDB^.data;
        end
        else begin
          New(curRow^.next);
          curRow := curRow^.next;
        end;
        SetLength(curRow^.value, cnt);
        curRow^.marker := true;
        curRow^.next := nil;

        // Werte in der Zeile speichern
        TmpVal := myTmpVal;
        n := 0;
        while (TmpVal <> nil) do begin
          if (n < cnt) then begin
            curRow^.value[n] := TmpVal^.value;
            TmpVal := TmpVal^.next;
          end;
          Inc(n);
        end;
      end;
    end;
    Result := true;
  end;

  {$I+}

  // Datei schließen
  if (FileOpened = true) then begin
    CloseFile(DBFile);
  end;
end;

// [...]
Hui, ist doch noch viel Code übrig geblieben... hoffe Ihr steigt durch!

Vielen Dank schonmal im Voraus für Eure Hilfe!
  Mit Zitat antworten Zitat
Benutzerbild von kiar
kiar

Registriert seit: 2. Aug 2003
Ort: Aschersleben
1.362 Beiträge
 
Delphi 5 Professional
 
#2

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 14. Apr 2005, 16:23
hallo,

willkommen in der dp

hast du schon mal deine Anwendung mit memproof gestartet?

wäre erstmal ein erster Versuch.

raik
verhältnisse die einem nicht passen,
muss man verändern oder verlassen
  Mit Zitat antworten Zitat
Basilikum

Registriert seit: 9. Aug 2003
389 Beiträge
 
Delphi 7 Professional
 
#3

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 14. Apr 2005, 16:29
ein Grund könnten auch die vielen ShortStrings sein, die - egal ob sie '' oder einen riesen String enthalten - pauschal 256 Byte belegen.... ev. lässt sich der Speicherverbrauch bereits mit String[x] reduzieren, wenn man weiss, das gewisse Felder nie mehr als x Zeichen enthalten werden (String[X] ist genau wie ShortStrings problemlos in/mit DLLs).
  Mit Zitat antworten Zitat
jbg

Registriert seit: 12. Jun 2002
3.483 Beiträge
 
Delphi 10.1 Berlin Professional
 
#4

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 14. Apr 2005, 16:32
Zitat:
Das ganze soll später mal in eine DLL verpackt werden, daher arbeite ich mit ShortStrings.
Und warum benutzt du dann dynamische Arrays?

Zitat:
sValue := sValue + pHaystack[i];
Damit hebelst du schon mal die Speichermanagerlogik aus und das Programm fängt an virtuellen Speicher zwischenzuspeichern. (das ist aber kein Speicherleck und wird somit von MemProof nicht angezeigt. Den Grund für das Verhalten kannst du hier im Forum mit der Suchfunktion finden).
  Mit Zitat antworten Zitat
Benutzerbild von Bernhard Geyer
Bernhard Geyer

Registriert seit: 13. Aug 2002
17.171 Beiträge
 
Delphi 10.4 Sydney
 
#5

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 14. Apr 2005, 17:53
Zitat von Eskayp:
Eine von mir programmierte Unit soll ein verschlüsseltes Datenbankfile einlesen und daraus Daten filtern und zurückgeben können.
...
Ach, fragt bitte nicht, warum ich nicht ne fertige DB-Schnittstelle nutze Smile hat so seine Gründe...
Für verschlüsselte Datenbankfiles könntest Du auch gleiche eine fertige Datenbank nehmen (Evtl. sprechen deine Geheimen Gründe dagegen). ADS Local Server unterstützt das verschlüsseln seiner Datenbankdateien.

Zitat von Eskayp:
Das Datenbankfile hat verschlüsselt (nicht komprimiert) gerade mal 260 KB. Wenn ich dies in meiner Anwendung nur einlese und in den verketteten Records speichere, braucht meine Anwendung erstens mehrere Sekunden und belegt dann zweitens 21 MB im Ram (ohne geladene Daten nur 2.5 MB).
Zur überprüfung von Speicherlöchern könntest Du auch MemCheck verwenden.
Windows Vista - Eine neue Erfahrung in Fehlern.
  Mit Zitat antworten Zitat
Benutzerbild von Speedmaster
Speedmaster

Registriert seit: 4. Mär 2005
Ort: Karlsruhe
535 Beiträge
 
Delphi 2005 Personal
 
#6

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 14. Apr 2005, 17:55
Willkommen in der DP², und eins muss man dir gleich lassen: Super Kommentiert der Code!
Felix K.
  Mit Zitat antworten Zitat
Eskayp

Registriert seit: 14. Apr 2005
5 Beiträge
 
#7

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 15. Apr 2005, 10:22
Wow, das geht ja schnell hier

Hmm, also Memproof scheint anzuzeigen (wenn ich das Tool richtig verstehe), dass es halt viele viele reservierte Speicherbereiche gibt, die alle die gleiche Größe haben. Das werden dann also die einzelnen Zeilen als Records sein.

Jetzt stellt sich nur die Frage: wie kann ich denn nun die Größe reduzieren? Die Strings sind natürlich unterschiedlich lang. Es handelt sich bei einer Tabelle z.B. um eine Fahrzeugtabelle. Da gibt es dann Fahrzeuge der Marke "BMW" und Fahrzeuge der Marke "Mitsubishi". Insgesamt ist das längste Feld 25 Zeichen lang, das kürzeste 0. Mit Strings einer festen Länge zu arbeiten ist also auch nicht sinnvoll.

Bringt es eigentlich was, packed records daraus zu machen? Muss ich mal ausprobieren.

---

Neben der Verschlüsselung gibt es noch andere Gründe, das Datenhandling selber zu übernehmen:
1. Da es sich um eine Auftragsarbeit handelt und das fertige Programm auf CD verteilt werden soll, muss ich genauestens auf Lizenzen achten.
2. Da sich das Datenformat ändern könnte (ist im Moment noch nicht abzusehen) müsste die Datenbankroutine ggf. ausgetauscht werden. Daher wird diese auch später in eine DLL ausgelagert.
3. Die selbe DLL wird dann wahrscheinlich auch für andere Projekte genutzt werden. Und da ist es schon besser, wenn man weiss, was da genau drin steckt

---

@Speedmaster: Vielen Dank für das Lob da ich normalerweise überwiegend in PHP programmiere, muss ich mir selber immer sagen, was ich denn da getan habe, hehe...

---

@jpg: Den dyn. Arrays liegt folgende Überlegung zugrunde: ich möchte mit ja im Prinzip mit Hilfe meiner Records ein zweidimensionales System mit ein paar Zusatzdaten abbilden. Wenn ich jetzt nach einem Datensatz des Herstellers "Mercedes" suche und die dazugehörigen Fahrzeuge ausgeben möchte, muss ich ja erstmal gucken, welche Spalte den Titel "Hersteller" trägt, und welche den Titel "Modell". Dann müsste ich in jeder Zeile die verk. Records von vorne nach hinten durchlaufen. Das wollte ich mir mit den dyn. Arrays sparen. Man hätte dann allerdings auch gleich ein kompl. 2-dim. Array machen können. Hmm. So hab ich ne Mischung gemacht, die jetzt im Nachhinein tatsächlich völlig bekloppt ist *g*
Ich änder das!

Aber wie mach ich das "sValue := sValue + pHaystack[i];" denn besser?
  Mit Zitat antworten Zitat
Benutzerbild von jim_raynor
jim_raynor

Registriert seit: 17. Okt 2004
Ort: Berlin
1.251 Beiträge
 
Delphi 5 Standard
 
#8

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 15. Apr 2005, 10:28
Zitat von Eskayp:
Aber wie mach ich das "sValue := sValue + pHaystack[i];" denn besser?
Such mal nach Explode in der Codelib. Das soll doch die Funktion machen oder?
Christian Reich
Schaut euch mein X-COM Remake X-Force: Fight For Destiny ( http://www.xforce-online.de ) an.
  Mit Zitat antworten Zitat
Christian Seehase
(Co-Admin)

Registriert seit: 29. Mai 2002
Ort: Hamburg
11.106 Beiträge
 
Delphi 11 Alexandria
 
#9

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 15. Apr 2005, 11:16
Moin Eskayp,

packed records werden Dir vor allem einmal einen Geschwindigkeitsverlust bringen

Wie viele Sätze/Felder hat denn die Datei?
Tschüss Chris
Die drei Feinde des Programmierers: Sonne, Frischluft und dieses unerträgliche Gebrüll der Vögel.
Der Klügere gibt solange nach bis er der Dumme ist
  Mit Zitat antworten Zitat
Eskayp

Registriert seit: 14. Apr 2005
5 Beiträge
 
#10

Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram

  Alt 15. Apr 2005, 11:43
Ok, die Explode-Routine hab ich neu geschrieben und ein wenig optimiert.
Jetzt müssen nicht mehr so viele New- und Dispose-Befehle ausgeführt werden.

Delphi-Quellcode:
// Das Aufsplitten einer Zeile anhand des SepStrings übernimmt diese Routine.
// Das Ergebnis wird in einem verketteten, temporärem Record gespeichert. So
// kann es dann später leicht in verschiedene Datenformen umgewandelt werden.
procedure MyDBExplode(pHaystack: String);
var
  i, n: Integer;
  TmpVal, NextTmpVal: PTmyTmp;
begin
  // Optimierung:
  // Da eigentlich alle Felder gleich lang sind, muss myTmpVal nicht jedesmal
  // gelöscht werden, sondern kann wiederverwendet werden. Evntl. muss dafür
  // das Feld aber dann eben mittendrin erweitert werden, oder eben am Ende
  // gestutzt.
  TmpVal := myTmpVal;

  n := 0;

  // Jetzt nach den Trennzeichen suchen und Ergebnisse im verk. Record
  // nyTmpVal speichern.
  i := Pos(mySepStr, pHaystack);
  while (i > 0) do begin
    // Diesen gefundenen Wert jetzt im verk. Record speichern
    if (myTmpVal = nil) then begin
      New(myTmpVal);
      TmpVal := myTmpVal;
      TmpVal^.next := nil;
    end
    else if (n = 0) then begin
      // nichts tun
    end
    else if (TmpVal^.next = nil) then begin
      New(TmpVal^.next);
      TmpVal := TmpVal^.next;
      TmpVal^.next := nil;
    end
    else begin
      TmpVal := TmpVal^.next;
    end;
    TmpVal^.value := Copy(pHaystack, 1, i - 1);

    // Haystack um das gefundene reduzieren und weiter suchen
    pHaystack := Copy(pHaystack, i + 1, Length(pHaystack) - i);
    i := Pos(mySepStr, pHaystack);
    Inc(n);
  end;
  // Letzten gefundenen Wert natürlich auch noch hinzufügen
  if (pHaystack <> '') then begin
    if (myTmpVal = nil) then begin
      New(myTmpVal);
      TmpVal := myTmpVal;
      TmpVal^.next := nil;
    end
    else if (n = 0) then begin
      // nichts tun
    end
    else if (TmpVal^.next = nil) then begin
      New(TmpVal^.next);
      TmpVal := TmpVal^.next;
      TmpVal^.next := nil;
    end
    else begin
      TmpVal := TmpVal^.next;
    end;
    TmpVal^.value := pHaystack;
  end;

  // Wenn es jetzt nach dem letzten gesp. Wert noch weitere Felder gibt
  // müssen diese gelöscht werden.
  if (TmpVal <> nil) then begin
    NextTmpVal := TmpVal^.next;
    TmpVal^.next := nil;
    TmpVal := NextTmpVal;
    while (TmpVal <> nil) do begin
      NextTmpVal := TmpVal^.next;
      Dispose(TmpVal);
      TmpVal := NextTmpVal;
    end;
  end;
end;
Außerdem habe ich einen ziemlich gravierenden Fehler gefunden:
Delphi-Quellcode:
  // Werte in der Zeile speichern
  TmpVal := myTmpVal;
  n := 0;
  while (TmpVal <> nil) do begin
    if (n < cnt) then begin
      curRow^.value[n] := TmpVal^.value;
      TmpVal := TmpVal^.next;
    end;
    Inc(n);
  end;
muss natürlich lauten:
Delphi-Quellcode:
  // Werte in der Zeile speichern
  TmpVal := myTmpVal;
  n := 0;
  while (TmpVal <> nil) do begin
    if (n < cnt) then
      curRow^.value[n] := TmpVal^.value;
    TmpVal := TmpVal^.next;
    Inc(n);
  end;
bzw. noch besser:
Delphi-Quellcode:
  // Werte in der Zeile speichern
  TmpVal := myTmpVal;
  n := 0;
  while ((TmpVal <> nil) and (n < cnt)) do begin
    curRow^.value[n] := TmpVal^.value;
    TmpVal := TmpVal^.next;
    Inc(n);
  end;
Die Einführung von packed records hat so gut wie gar nichts gebracht. Dafür aber die Reduzierung von Array of Shortstring auf Array of String[25]. Und zwar ganz gewaltig! Vielen Dank schonmal an Basilikum für diesen Hinweis. Jetzt muss ich noch das dyn. Array wegrationalisieren
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:45 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz