Memory-Leck? Verkettete Records brauchen extrem viel Ram...
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:
Hui, ist doch noch viel Code übrig geblieben... hoffe Ihr steigt durch!
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; // [...] Vielen Dank schonmal im Voraus für Eure Hilfe! |
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
hallo,
willkommen in der dp :mrgreen: hast du schon mal deine Anwendung mit memproof gestartet? wäre erstmal ein erster Versuch. raik |
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
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).
|
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
Zitat:
Zitat:
|
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
Zitat:
Zitat:
|
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
Willkommen in der DP², und eins muss man dir gleich lassen: Super Kommentiert der Code!
|
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
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? |
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
Zitat:
|
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
Moin Eskayp,
packed records werden Dir vor allem einmal einen Geschwindigkeitsverlust bringen ;-) Wie viele Sätze/Felder hat denn die Datei? |
Re: Memory-Leck? Verkettete Records brauchen extrem viel Ram
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:
Außerdem habe ich einen ziemlich gravierenden Fehler gefunden:
// 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;
Delphi-Quellcode:
muss natürlich lauten:
// 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;
Delphi-Quellcode:
bzw. noch besser:
// 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;
Delphi-Quellcode:
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 ;)
// 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; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:33 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