| Autor |
Beitrag |
AScomp
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 00:04
Hallo,
zum Schreiben eines Logs nutze ich derzeit eine TStringList, die per AddObject jeweils einen Dateinamen sowie den zugehörigen Zeitstempel zugewiesen bekommt. Da es sehr umfangreiche Operationen sind, kann das Log durchaus mehrere hundertausend Einträge enthalten - was früher oder später zu einem EOutOfMemory führt. Ich vermute, das liegt am Speichermanager von Delphi XE (zumindest glaube ich mich zu erinnern, dass Delphi mit SetLength schon früher Probleme hatte, was vermutlich bei AddObject intern verwendet wird, um einen neuen/größeren zusammenhängenden Speicherbereich zu erhalten).
Jetzt wäre also die Frage, wie ich dieses Problem in den Griff bekommen kann. Da im Voraus nicht klar ist, wie viele Einträge es im Log geben wird, kann ich die Größe schlecht vorher bestimmen. Natürlich könnte ich einfach auf Verdacht mal 10.000 Einträge hinzufügen und sobald der 10.001. kommt, weitere 10.000 hinzufügen usw. Aber ich hoffe, dass es eine elegantere Möglichkeit gibt.
Hat mir jemand einen Tipp?
Vielen Dank!
Liebe Grüße
Andy
|
|
bummi
      
Beiträge: 1248
Erhaltene Danke: 187
XP - Server 2008R2
D2 - Delphi XE
|
Verfasst: Mi 09.01.13 00:18
Brauchst Du das komplette LOG im Speicher, sonst könntest Du zwischendurch in Dateien oder Datenbanken "wegstreamen"
_________________ Das Problem liegt üblicherweise zwischen den Ohren H₂♂
DRY DRY KISS
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 00:27
Ich brauche es zwar nicht unbedingt im Speicher, allerdings ist das "wegspeichern" in eine Datei sehr langsam - und nur wegen eines Logs möchte ich keine Datenbankanbindung integrieren.
|
|
Tranx
      
Beiträge: 648
Erhaltene Danke: 85
WIN 2000, WIN XP
D5 Prof
|
Verfasst: Mi 09.01.13 00:47
Ich weiß nicht, ob das funktioniert, aber vielleicht versuchst Du es mal mit TList.
Das ist dann bei der Einfügung nicht mehr ganz so einfach, denn Du müsstest Dein Objekt als z.B. Record definieren, der TList dann einen Zeiger auf den Record übergeben, der bei jedem Hinzufügen allerdings erst erzejugt werden muss. Auch das Löschen Der Liste gestaltet sich dann etwas schwieriger, da Du ja jeden Eintrag erst löschen musst, bevor Du die Liste löschst.
Du hättest dann eine Listen nur mit Pointern. Die nehmen weniger Platz weg. Und die Einträge können ja irgendwo im Speicher sein, müssen ja nicht zusammenhängend sein. Vielleicht klappt das für Deinen Fall.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90:
| unit ListeUnit;
interface
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Grids, StdCtrls, Buttons;
type TForm1 = class(TForm) btnHinzu: TButton; btnloeschen: TButton; btnanzeigen: TButton; ergebnis: TStringGrid; btnNeueListe: TBitBtn; procedure btnHinzuClick(Sender: TObject); procedure btnNeueListeClick(Sender: TObject); procedure btnanzeigenClick(Sender: TObject); procedure btnloeschenClick(Sender: TObject); private public end;
type pObjRec = ^ObjRec; ObjRec = record Dateiname : string; Zeitstempel : TDateTime; end; var Form1: TForm1; Liste : TList; Eintrag : pObjRec;
implementation
{$R *.DFM}
procedure TForm1.btnHinzuClick(Sender: TObject); begin New(Eintrag); Eintrag^.Dateiname := 'NeuerEintrag '+intToStr(Liste.Count+1); Eintrag^.Zeitstempel := Now; Liste.Add(Eintrag); btnanzeigen.Enabled := True; end;
procedure TForm1.btnNeueListeClick(Sender: TObject); begin Liste := TList.Create; btnNeueListe.Enabled := False; btnHinzu.Enabled := True; btnloeschen.Enabled := True; end;
procedure TForm1.btnanzeigenClick(Sender: TObject);
var i : Integer; Eintr : pObjRec; begin ergebnis.Cells[0,0] := 'Eintragstext'; ergebnis.Cells[1,0] := 'Datum'; for i := 0 to Liste.Count-1 do begin Eintr := Liste[i]; ergebnis.Cells[0,i+1] := Eintr^.Dateiname; ergebnis.Cells[1,i+1] := DateTimeToStr(Eintr^.Zeitstempel); end; ergebnis.RowCount := Liste.count+1; end;
procedure TForm1.btnloeschenClick(Sender: TObject); var i : Integer; Eintr : pObjRec; begin for i := 0 to Liste.Count-1 do begin Eintr := Liste[i]; Dispose(Eintr); end; Liste.Free; btnanzeigen.enabled := False; btnHinzu.Enabled := False; btnloeschen.Enabled := False; btnNeueListe.Enabled := True; end;
end. |
(Ein Formular mit mehreren Buttons und einem Stringgrid)
_________________ Toleranz ist eine Grundvoraussetzung für das Leben.
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 00:53
Danke, das wäre auch eine Idee!
Ich frage mich momentan aber, ob nicht einfach ein Setzen der Capacity auf einen definitiv "sicheren" Wert nicht eine Lösung wäre. Also beim Erstellen der TStringList gleich die Capacity z.B. auf 1000000 setzen:
BkmLogFiles := TStringList.Create;
BkmLogFiles.Capacity := 1000000;
Allerdings frage ich mich, inwiefern Capacity im Voraus schon wissen kann, wieviel Speicherplatz die ja erst später hinzugefügten Objects nachher tatsächlich benötigen.
|
|
Gerd Kayser
      
Beiträge: 632
Erhaltene Danke: 121
Win 7 32-bit
Delphi 2006/XE
|
Verfasst: Mi 09.01.13 00:57
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 01:00
Hallo Gerd,
sorry, hatte gerade die Zitieren-Funktion für PN genutzt und dir wohl privat geantwortet...
Es geht nicht um ein "kleines" Log, sondern wie im ersten Beitrag geschrieben um meist mehrere hundertausend Dateieinträge plus Zeitstempel zu jeder Datei.
|
|
Gerd Kayser
      
Beiträge: 632
Erhaltene Danke: 121
Win 7 32-bit
Delphi 2006/XE
|
Verfasst: Mi 09.01.13 01:04
AScomp hat folgendes geschrieben : | | Es geht nicht um ein "kleines" Log, sondern wie im ersten Beitrag geschrieben um meist mehrere hundertausend Dateieinträge plus Zeitstempel zu jeder Datei. |
Im Initialization-Teil der Unit, die die Protokollprozedur enthält, öffnest Du die Datei, im Finalization-Abschnitt machst Du sie wieder zu. Dann kannst Du doch zigtausende Protokollzeilen schreiben.
|
|
Narses
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: Mi 09.01.13 01:08
Moin!
Was passiert denn mit dem Log? Musst du indiziert zugreifen können? Wenn nicht, brauchst du auch keine Array-Basierte Komponente (übrigens bezweifle ich, dass sich TStringList und TList irgendwie bei diesem Einsatz großartig unterscheiden). Dann wäre eine LinkedList deutlich besser!
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 01:11
Hallo Narses,
ja, ich muss indiziert darauf zugreifen - es muss außerdem sortiert werden (eben, damit ich schneller darauf zugreifen kann).
Darum macht das mit dem direkten Wegschreiben in eine Datei weniger Sinn. Zudem ist das ständige Schreiben auch recht langsam, und obendrein ist es kein "Programmlog", sondern ein "Aufgabenlog", sprich: Es werden mehrere Logs pro Programmsession geschrieben, nicht nur eines.
Mich würde brennend interessieren, wie Capacity genau arbeitet bzw. ob ich damit nicht einfach von vornherein ausreichend Platz für die später hinzugefügten Objects reservieren kann.
|
|
Narses
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: Mi 09.01.13 01:18
Moin!
AScomp hat folgendes geschrieben : | | Mich würde brennend interessieren, wie Capacity genau arbeitet bzw. ob ich damit nicht einfach von vornherein ausreichend Platz für die später hinzugefügten Objects reservieren kann. |
Kurz: .Capacity legt die Blockgröße fest, um die die Liste erweitert wird, wenn diese intern "voll" ist.
Es könnte also durchaus in deinem Fall helfen, einen "geeigneten" Wert für .Capacity zu wählen. Probier´s erstmal mit 2^20 = 1.048.576.
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 02:20
Danke euch, ich werde es erstmal mit Capacity ausprobieren.
Momentan sind nur zwei Anwender davon betroffen, trotzdem hoffe ich, bald ein Feedback zu bekommen, ob die Änderung etwas gebracht hat.
Liebe Grüße
Andy
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 14:38
Capacity hat leider keine Besserung gebracht - es erscheint nach wie vor eine EOutOfMemory Exception.
Ich bin jetzt dabei, die StringList durch ein Array zu ersetzen, vielleicht funktioniert es damit besser.
|
|
thepaine91
      
Beiträge: 763
Erhaltene Danke: 27
Win XP, Windows 7, (Linux)
D6, D2010, C#, PHP, Java(Android), HTML/Js
|
Verfasst: Mi 09.01.13 16:01
Also ich finde die Lösung über eine SQLLite Datenbank am besten.
Die maximale Größe müsste 2Tb betragen und sollte somit problemlos ausreichen.
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 16:20
Das Array hat letzten Endes genau dasselbe Problem - irgendwann wird es einfach zu groß.
Jetzt wird das Log also doch direkt weggeschrieben in eine Datei. Geht wohl bei der enormen Datenmenge nicht anders (zumindest, wenn man auf eine Datenbank verzichten möchte).
|
|
Narses
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: Mi 09.01.13 16:24
Moin!
AScomp hat folgendes geschrieben : | | Capacity hat leider keine Besserung gebracht - es erscheint nach wie vor eine EOutOfMemory Exception. |
Die spannende (und IMHO entscheidende) Frage ist, bei welcher Aktion und unter welchen Umständen exakt die Problematik auftritt. Das sollte dann ja ein reproduzierbarer UseCase sein. Ich würde dir empfehlen, eine entsprechende Testumgebung/-Fall aufzusetzen, sonst wird das immer Fischen-im-Trüben bleiben.
AScomp hat folgendes geschrieben : | | Ich bin jetzt dabei, die StringList durch ein Array zu ersetzen, vielleicht funktioniert es damit besser. |
AScomp hat folgendes geschrieben : | | Das Array hat letzten Endes genau dasselbe Problem - irgendwann wird es einfach zu groß. |
Genau das meine ich, liegt es wirklich nur an einer ineffizienten/ungeeigneten Verwaltung/Datenstruktur oder nicht doch an der Datenmenge. Das wirst du nur mit einer Testumgebung/-Fall rauskriegen.
Liegt es an der Datenmenge, ist da kein Kraut gewachsen. Liegt´s an der Verwaltung, sollte man da was geeignetes finden können (z.B. die Daten doch in einer verketteten Liste halten und nur Index-Arrays im Zugriffsfall bilden, dann steht die Datenmenge ja fest und die Verwaltungsstruktur kann exakt alloziert werden).
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
Gerd Kayser
      
Beiträge: 632
Erhaltene Danke: 121
Win 7 32-bit
Delphi 2006/XE
|
Verfasst: Mi 09.01.13 17:18
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 17:30
@Gerd: Die Datenmenge lässt sich nicht reduzieren - wenn der Anwender 2 TB an Daten wegsichern möchte und ein Protokoll dazu braucht, dann ist die Datenmenge nunmal nicht kleiner zu bekommen.
Das Speichern in einer Datei od. Datenbank ist vermutlich der einzig gangbare Weg, um diese Datenmengen verwalten zu können.
Zwischenzeitlich hatte ich auch über eine Komprimierung der Daten im Speicher nachgedacht, allerdings bringt das insofern wenig, weil der Zeitaufwand für die Komprimierung (und beim Zugriffsbedarf der Dekomprimierung) viel zu hoch wäre.
Mit dem Programmdesign hat das dann letzten Endes auch recht wenig zu tun.
@Narses: Ja, es liegt an der Datenmenge. Das bisherige System lief über Jahre hinweg einwandfrei. Doch inzwischen gibt es immer mehr Anwender, die teilweise Datenmengen im Terabyte-Bereich wegsichern möchten. Das macht ein im Speicher gehaltenes Log natürlich unmöglich, zumal wie oben beschrieben eine Komprimierung nicht in Frage kommt.
P.S. Wobei natürlich nicht die Datenmenge an sich das Problem darstellt, sondern die hohe Anzahl an Dateien/Dateinamen.
|
|
jaenicke
      
Beiträge: 19339
Erhaltene Danke: 1752
W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: Mi 09.01.13 17:39
Also ich kann mit Delphi XE problemlos innerhalb eines Wimpernschlags (paar Millisekunden halt) auch eine Million Zeilen mit je 100 Zeichen in eine TStringList schreiben und in nicht einmal 2 Sekunden in eine am Ende 100 MB große Textdatei schreiben...
Das Problem liegt glaube ich woanders...
// EDIT:
EOutOfMemory kommt z.B. auch bei überschriebenem Speicher vor. Hast du schon einmal FastMM im FullDebugMode benutzt? Vielleicht zeigt das ja etwas an.
|
|
AScomp 
      
Beiträge: 162
Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
|
Verfasst: Mi 09.01.13 17:43
jaenicke hat folgendes geschrieben : | Also ich kann mit Delphi XE problemlos innerhalb eines Wimpernschlags (paar Millisekunden halt) auch eine Million Zeilen mit je 100 Zeichen in eine TStringList schreiben und in nicht einmal 2 Sekunden in eine am Ende 100 MB große Textdatei schreiben...
Das Problem liegt glaube ich woanders...
// EDIT:
EOutOfMemory kommt z.B. auch bei überschriebenem Speicher vor. Hast du schon einmal FastMM im FullDebugMode benutzt? Vielleicht zeigt das ja etwas an. |
Das ist nicht das Problem. Die Geschwindigkeit ist generell nicht das Problem.
Aber du kannst es recht einfach nachstellen:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| procedure TForm1.Button1Click(Sender: TObject); var SL: TStringList; FileDateValue: TFileDateValue; StartTime: DWORD; i, ItemsToAdd: integer; begin SL := TStringList.Create; ItemsToAdd := StrToInt(Edit1.Text); if CheckBox1.Checked then SL.Capacity := SL.Count + ItemsToAdd; StartTime := GetTickCount; for i := 1 to ItemsToAdd do begin FileDateValue := TFileDateValue.Create; FileDateValue.DateTimeValue := Now; SL.AddObject('C:\verzeichnis\unterverzeichnis\recht langes unterverzeichnis\der dateiname mit.extension', FileDateValue); end; Label1.Caption := IntToStr(GetTickCount - StartTime); SL.Free; end; |
Du brauchst nur ein Edit, eine CheckBox, einen Button und ein Label. 10 Millionen Einträge sind ganz schnell geschrieben - macht auch fast keinen Unterschied, ob du die Capacity vorher setzt oder nicht. ABER: Es wird eine EOutOfMemory-Exception ausgelöst (in Abhängigkeit vom zur Verfügung stehenden Arbeitsspeicher früher oder später).
Bitte nicht wundern, dass die FileDateValues in diesem Beispiel nicht mehr freigegeben werden. Es ging da nur um ein schnelles Beispiel, dasselbe funktioniert auch ohne FileDateValues.
Zuletzt bearbeitet von AScomp am Mi 09.01.13 17:51, insgesamt 3-mal bearbeitet
|
|
|