Autor Beitrag
AScomp
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 1248
Erhaltene Danke: 187

XP - Server 2008R2
D2 - Delphi XE
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 648
Erhaltene Danke: 85

WIN 2000, WIN XP
D5 Prof
BeitragVerfasst: 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.

ausblenden volle Höhe Delphi-Quelltext
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
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 632
Erhaltene Danke: 121

Win 7 32-bit
Delphi 2006/XE
BeitragVerfasst: Mi 09.01.13 00:57 
user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
Da im Voraus nicht klar ist, wie viele Einträge es im Log geben wird, kann ich die Größe schlecht vorher bestimmen.

Wenn es nur darum geht, Fehlermeldungen in eine Logdatei wegzuschreiben, würde ich AssignFile, Rewrite, Append usw. empfehlen. Für das Schreiben der Einträge dann eine entsprechende Prozedur. Zum Beispiel (Auszug):

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure Protokoll(Idx: Integer; Zeile: String);
var
  Temp : String;
begin
  case Idx of
    0: Temp := '-PROG- ';
    1: Temp := '-INIT- ';
    2: Temp := '-IMPORT- ';
    3: Temp := '-KONFIG- ';
    4: Temp := '-ABGLEICH- ';
  end;

  Temp := FormatDateTime('hh:nn:ss.zzz ', now) + Temp;
  Writeln(Protokolldatei, Temp + Zeile);
end;


Ansprechen dann zum Beispiel so:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
  if not DirectoryExists(Ausgangsverzeichnis) then
    begin
      ForceDirectories(Ausgangsverzeichnis);
      Protokoll(1'Verzeichnis ' + Ausgangsverzeichnis + ' wurde angelegt.');
    end;
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 632
Erhaltene Danke: 121

Win 7 32-bit
Delphi 2006/XE
BeitragVerfasst: Mi 09.01.13 01:04 
user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: 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! :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Mi 09.01.13 01:18 
Moin!

user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
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. :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 763
Erhaltene Danke: 27

Win XP, Windows 7, (Linux)
D6, D2010, C#, PHP, Java(Android), HTML/Js
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: Mi 09.01.13 16:24 
Moin!

user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
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. :nixweiss:

user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
Ich bin jetzt dabei, die StringList durch ein Array zu ersetzen, vielleicht funktioniert es damit besser.
user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
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). :idea:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
Gerd Kayser
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 632
Erhaltene Danke: 121

Win 7 32-bit
Delphi 2006/XE
BeitragVerfasst: Mi 09.01.13 17:18 
user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Liegt es an der Datenmenge, ist da kein Kraut gewachsen.

Sollte man nicht eher darüber nachdenken, ob und wie man die Datenmenge reduzieren kann?

user profile iconAScomp hat folgendes geschrieben Zum zitierten Posting springen:
mehrere hundertausend Dateieinträge plus Zeitstempel zu jeder Datei.

Da lohnt es sich sicherlich, einige Stunden mal über das Programmdesign nachzudenken.
AScomp Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19339
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 162


Delphi 5, Delphi 7, Delphi 2007, Delphi 2009, Delphi XE, Delphi 10 Seattle
BeitragVerfasst: Mi 09.01.13 17:43 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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:

ausblenden 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;
  // GetTickCount gives the number of millisecs since Windows started
  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