Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Große Textdatei - einzelne Zeile löschen (https://www.delphipraxis.net/211189-grosse-textdatei-einzelne-zeile-loeschen.html)

GummiKuh68 10. Aug 2022 14:36

Große Textdatei - einzelne Zeile löschen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Moin, moin,

...'...oh Nein, nicht schon wieder Textdatei...' möge manch einer jetzt denken...hatten wir schon!
Ja und habe ich auch alles gelesen - passt aber alles nicht!

Problem:
Ich habe eine Datei die ca. 97 GB groß ist (Klartext: rockyou2021.txt!).
Eins vorneweg - das Problem mit ähnlich großen Dateien habe ich auch während des Supports, wenn ich Messdaten im Rahmen der Fehlersuche analysieren muss.

Nun ist es ja so, dass man Dateien ab einer gewissen Größe nicht mal ebenso in einen Editor laden kann.
Notepad++, PilotEdit, PSPad, usw. machen da irgendwann schlapp! UltraEdit soll das angeblich können, aber da der Download der Demo nicht klappt, kann ich dazu wenig sagen! :roll:

Meine Idee war nun, Zeile für Zeile aus der Datei zu lesen und gleich in eine andere Datei zu schreiben, deren Dateiname sich vom Original unterscheidet (Zusatz im Dateinamen: ASCII-Wert des ersten Zeichens!).
Das habe ich auch schon hinbekommen, dass läuft sauber durch.
Das Dumme daran ist, das ich dafür den Rechner die nächste Tage durchlaufen lassen muss - wenn ich das mache, habe ich gleich GAAAAANNZZ andere Probleme! :shock:

Aktuell benutze ich folgenden Code:

Delphi-Quellcode:
procedure TForm1.cb3Click(Sender: TObject);
var
  InFile : TextFile;
  i,cnt,s : integer;
  Rest,a: string;
  b      :Char;
begin
  assignFile (Infile, 'I:\rockyou2021.txt');
  reset (Infile);
  while not eof (InFile) do
    begin
      readln (InFile, Rest);
      if Rest <> '' then
        begin
          b:=Rest[1];
          of_insstr('I:\rockyou2021-'+inttostr(ord(b))+'.txt',Rest);
        end;
    end;
  CloseFile (InFile);
end;

procedure TForm1.of_insstr(OFile,fstr:string);
var
  OutFile :TextFile;
  i,cnt,s : integer;
  Rest,a,b: string;
begin
  //
  if FileExists(OFile) then
    begin
      assignFile (OutFile, OFile);
      append(OutFile);
    end
  else
    begin
      assignFile (OutFile, OFile);
      rewrite(OutFile);
    end;
  Writeln(OutFile,fstr);
  CloseFile (OutFile);
end;
Ich hab das jetzt auch schon mal laufen lassen und war auch mit dem Ergebnis bisher zufrieden. Allerdings werde ich das wegen der Dauer (habe nach 4 Stunden abgebrochen), immer wieder neu starten müssen.
Das bedeutet aber, das das Programm die Datei von Anfang an ließt und wenn ich die erzeugten Dateien nicht wegschmeiße, alles doppelt reinschreibt, was Mist ist!

Daher war meine Idee, dass ich die Zeile, die ich gerade in eine andere Datei geschrieben habe aus der Ursprungsdatei lösche.
Wenn ich den Code in cb3Click jetzt wie folgt anpasse:

Delphi-Quellcode:
  assignFile (Infile, 'I:\rockyou2022.txt');
  reset (Infile);
  while not eof (InFile) do
    begin
      readln (InFile, Rest);
      if Rest <> '' then
        begin
          b:=Rest[1];
          of_insstr('I:\rockyou2021-'+inttostr(ord(b))+'.txt',Rest);
          writeln(InFile, ''); // '' wäre ok, obwohl noch #13#10 bleibt! Ganz raus wäre noch besser!
        end;
    end;
  CloseFile (InFile);
...bekomme ich zurecht einen Fehler, da die Textdatei mit Reset() nur für das Lesen geöffnet wird.

Was übersehe ich hier?

Ich hatte schon über TStringList nachgedacht aber wenn ich die ca. 97 GB da reinlade...??

TFileStream hatte ich auch schonmal als Idee im Hinterkopf, aber keinen Plan, da ich mit FileStreams bisher eher weniger zu tun hatte.
Zum Testen habe ich mir natürlich eine kleinere Datei gebastelt (s. Anhang).

Vielleicht hat da ja jemand von euch eine Idee!?

Rollo62 10. Aug 2022 20:20

AW: Große Textdatei - einzelne Zeile löschen
 
Also gefühlt würde ich sagen dass ein zeilenweises Einlesen da nicht ganz optimal ist.
Hast Du mal versucht das Ganze blockweise einzulesen, z.B. 64K Blöcke oder mehr, und die dann im Speicher zu analysieren und blockweise zurückzuschreiben ?
Das ist zwar wesentlich komplexer, aber ich denke das lohnt sich geschwindigkeitsmäßig.

himitsu 10. Aug 2022 20:53

AW: Große Textdatei - einzelne Zeile löschen
 
Zitat:

Was übersehe ich hier?
Daß du zwei Dateien brauchs? (Lesen + Schreiben)

Selbst wenn du vor/nach dem Lesen/Schreiben die Stream-Position "zurück"-setzt,
bekommst du Probleme, wenn die geschriebene Zeile länger ist, als die Gelesene, weil du damit ja bereits die nächste Zeile überschreibst, welche du noch garnicht gelesen hast.

Mit passenden Sharing-Rechten, kann man die gleiche Datei auch mehrmals öffnen, also Lesen und nochmal zum Schreiben.
Aber kann man auch mit nur einem File-Handle und zwei Cursor-Positionen.


Wobei es hier ginge, da Schreiben kürzer ist, als Lesen. (Zeile>'' lesen und Zeile='' schreiben)
Aber nein, SO kannst du keine Zeile löschen, denn die nachfolgenden Zeilen bleiben dennoch an der selben Stelle, was DU selbst verschieben müsstest.
Würdes du die Zeile mit einem '' löschen überschreiben, bliebe der Rest der alten Zeile dennoch erhalten (man könnte diese Zeile höchstsen in der selben Länge z.B. mit mehreren #0 oder ' ' überscheiben, ohne Nachfolgendes verschieben zu müssen)
Code:
** = Zeilenumbruch
_  = Leerzeichen, #0 oder sonstwas

Zeile1**Zeile2**Zeile3**Zeile4** ~ Original

NeueZeile1**e2**Zeile3**Zeile4** ~ Zeile1** durch NeueZeile1** überschrieben (Zeile2** nicht mehr lesbar)
xx**e1**Zeile2**Zeile3**Zeile4** ~ Zeile1** durch xx** überschrieben
**ile1**Zeile2**Zeile3**Zeile4** ~ Zeile1** durch ** überschrieben, aka WriteLn('')

______**Zeile2**Zeile3**Zeile4** ~ Zeile1** durch ______** überschrieben
________Zeile2**Zeile3**Zeile4** ~ Zeile1** durch ________ überschrieben
********Zeile2**Zeile3**Zeile4** ~ Zeile1** durch ******** überschrieben, also 4 Mal WriteLn(''), aber bei ungerader Länge hast'e ein kleines Problem
Zeile2**Zeile3**Zeile4**         ~ Zeile1** gelöscht und Nachfolgendes verschoben (schnell wird es so aber nicht ... weil ja massenhaft Speicher mehrmals verschoben wird)


WriteLn ohne #13#10 heißt Write :roll:




TStringList oder TStringStream
und notfalls als 64 Bit kompilieren (ja, neuere Delphis haben ein paar Vorteile)

Auch wenn eine 100M Datei vermutlich ebenfalls in 32 Bit funktioniert.
Man darf aber bedenken, dass der Speicherverbauch beim Laden/Speichern auch mal vorübergehend das 5-fache belegen kann, aber 500M passen ja noch. (5 bei Unicode, aber 2- bis 3-faches auch schon im D7 mit ANSI)




* "nutzlos" zu speichern/überschreiben macht es langsamer
* die alten "Pascal"-Textdatei-Funktionen arbeiten mit einem sehr unoptimalen Buffering = langsam
* TStringList ist nicht ganz optimal und braucht mehr Arbeitsspeicher, aber dennoch ist es viel schneller

* of_insstr sucht und öffnet/speichert immer wieder Dateien = langsam
* * die Dateien (FileHandles) sich zu merken und erst am Ende zu schließen wäre schneller (sind ja nur etwa 60 bis 255 Dateien)
* * oder die QuellDatei mehrmals durchlaufen und jeweils nur EINE "of_insstr"-Datei du behandeln ... zwar mehrmals Lesen aber immer nur jeweils eine Ausgabedatei in einem Rutsch

Uwe Raabe 10. Aug 2022 20:57

AW: Große Textdatei - einzelne Zeile löschen
 
Check doch erstmal, ob das Lesen der Datei der Bottleneck ist oder das Schreiben der Einzeldateien.

Der schöne Günther 10. Aug 2022 21:03

AW: Große Textdatei - einzelne Zeile löschen
 
Ist es schon zu spät für mich, oder machst du bei jeder einzelnen Zeile deine Zieldatei auf und wieder zu?

jaenicke 10. Aug 2022 21:09

AW: Große Textdatei - einzelne Zeile löschen
 
Ich würde hier mit MMFs arbeiten. Durch das Mapping in den Arbeitsspeicher ist das extrem schnell. Damals kam ich bei einer normalen Festplatte auf 84 MiB/s, was heute mit einer SSD noch viel schneller gehen sollte.

Ich habe dafür einen Wrapper geschrieben:
https://www.delphipraxis.net/151898-...ei-reader.html

Flamefire hat sich das angeschaut und das ganze mit Streams umgesetzt:
https://entwickler-ecke.de/viewtopic.php?t=100088

Für die beste Performance musst du da vielleicht noch ein wenig schrauben, aber zumindest siehst du dort wie du mit MMFs arbeiten kannst.

Monday 11. Aug 2022 06:41

AW: Große Textdatei - einzelne Zeile löschen
 
Kannst du mal bei Notepad++ das Plugin "BigFiles - Open Very Large Files" probieren? Würde mich interessieren, ob er das packt.

dummzeuch 11. Aug 2022 08:02

AW: Große Textdatei - einzelne Zeile löschen
 
Ich hatte mal ein Tool zum Anschauen großer Textdateien geschrieben, das ich damals mit einem Dump der Wikipedia getestet hatte (damals 48 GB).

Funktionsweise war, dass es in einem Hintergrund-Thread die Datei liest und einen Index der Zeilenumbrüche bzw. Zeilenanfänge erzeugt, den es in eine Datei schreibt, so dass der Index nicht jedes Mal neu erzeugt werden muss.

Vielleicht findest Du ja im Sourcecode ein paar Anregungen?

Wichtig: Die Suchfunktion habe ich nie fertiggestellt, nicht dass Du dich wunderst, dass sie nicht funktioniert.

freimatz 11. Aug 2022 08:54

AW: Große Textdatei - einzelne Zeile löschen
 
Zitat:

Zitat von GummiKuh68 (Beitrag 1509976)
Nun ist es ja so, dass man Dateien ab einer gewissen Größe nicht mal ebenso in einen Editor laden kann.
Notepad++, PilotEdit, PSPad, usw. machen da irgendwann schlapp! UltraEdit soll das angeblich können, aber da der Download der Demo nicht klappt, kann ich dazu wenig sagen! :roll:

Mal hier versucht Ultraedit, neueste Version, aber nur 50GByte und keine Textdatei sondern binär.
a) Nach einigen Minuten war er bei 50%, habe dann abgebrochen
b) Eine Option umgestellt, nochmals versucht, nach ca. 2 Sekunden war die Datei da.
Scrollen über die ganze Datei ist etwas hackelig, finde ich aber erstaunlich gut.

jaenicke 11. Aug 2022 09:03

AW: Große Textdatei - einzelne Zeile löschen
 
Ich würde das schlicht mit HxD machen, wenn es ein fertiges Programm sein soll. Dann fällt einiges weg, was ein Texteditor noch zusätzlich machen muss, und es muss wirklich nur die Änderung an der Datei geschrieben werden.

Delphi.Narium 11. Aug 2022 10:16

AW: Große Textdatei - einzelne Zeile löschen
 
Du benötigst für jeden vorkommenden Anfangsbuchstaben eine Datei.
Je Zeile prüfst Du, ob es diese Datei gibt, öffnest oder erstellst sie, schreibst hinein und schließt sie.

Welche Anfangsbuchstaben vorkommen, lässt sich sicherlich ermitteln.
Im Zweifel ist das alles von ASCII 32 bis 255. Vermutlich aber eher ASCII 49 bis 57 (für die Ziffern 0 bis 9), ASCII 65 bis 90 (für A bis Z) und ASCII 97 bis 122 (für a bis z). Eventuell noch sowas wie -_.

Erstelle Dir einmalig diese Dateien, dann kannst Du schonmal je Zeile das
Delphi-Quellcode:
  if FileExists(OFile) then
    begin
      assignFile (OutFile, OFile);
      append(OutFile);
    end
  else
einsparen.

Bei 97 GB (104.152.956.928 Byte) mit einer (geratenen) durchschnittlichen Zeilenlänge von 256 Byte, entfallen damit ca. 406.847.488 Prüfungen, ob eine Datei existiert oder nicht. Bei einer Dauer dieser Prüfung von jeweils 0,01 Sekunden, ergäbe der Wegfall dieser Prüfungen eine Laufzeitverringerung von ca. 47 Tagen.
Bei 64 KB pro Zeile wäre das noch 'ne Ersparnis von etwa 4,5 Tagen.

Wenn Du Dir nun die Dateien am Programmanfang erstellst bzw. öffnest, sie während der Programmlaufzeit offen hälst und erst zum Programmende schließt, sparst Du auch noch die ReWrites und CloseFiles je Zeile. Könnte dann auch die Laufzeit spürbar verkürzen.

Eventuell könnte ja sowas in der Art funktionieren (ungetestet hingedaddelt):
Delphi-Quellcode:
type
  rFile = record
    AFile    : TextFile;
    AFileName : String;
  end;
  TFiles = Array[32..255] of rFile;

var
  Files : TFiles;

procedure TForm1.cb3Click(Sender: TObject);
var
  i     : Integer;
  sZeile : String;
  inFile : TextFile;
begin
  for i := Low(Files) to High(Files) do begin
    Files[i].AFileName := Format('i:\rockyou2021-%.3d.txt',[i]);
    AssignFile(Files[i].AFile,Files[i].AFileName);
    case FileExists(Files[i].AFileName) of
      true : Append(Files[i].AFile);
      false : ReWrite(Files[i].AFile);
    end;
  end;
  AssignFile(Infile,'I:\rockyou2022.txt');
  Reset(Infile);
  while not EoF(InFile) do begin
    ReadLn(InFile, sZeile);
    if sZeile <> '' then WriteLn(Files[Ord(sZeile[1])].AFile,sZeile);
  end;
  CloseFile(InFile);
  for i := Low(Files) to High(Files) do CloseFile(Files[i].AFile);
end;

himitsu 11. Aug 2022 10:44

AW: Große Textdatei - einzelne Zeile löschen
 
Wenn man die Dateien/Streams selber öffnet (CreateFile), dann kann man auch das Windows-Caching positiv beeinflussen -> Windows sagen, dass man Sequentiell ließt/schreibt und nicht ramdom (kreuz und quer).
Dann statt AssignFile, mit seinem zusätzlichem extrem unoptimalen Caching, gäbe es bessere Stream-Funktionen,
aber zumindestens könnte man beim Reset/Rewirte (nicht Append) die RecSize etwas anpassen, damit der Cache im Delphi etwas flotter wird.


[edit] opps, 97 GB ... nicht MB :oops: [/edit]


egal: mit Win64 kompiliert und ausreichend Auslagerungsdatei (und natürlich freie Festplatte dafür)
oder ein kleiner 128GB oder 256GB RAM-Riegel
https://docs.microsoft.com/de-de/win...ndows-releases

* eine TStringList zum Laden
* danach ist jede Zeile als einzelner String im Speicher

* dann ein Array/Liste mit TStringList für jeden Anfangsbuchstaben
* * alle Ausgabedateien zu Beginn erstellen, oder erst wenn benötogt
* * beim Array kann man auf nil prüfen (mit SetLength vorher einfach auf High(Char)+1 setzen)
* * bei der TList, oder besser einem TDictionary (tja, selbst schuld, wenn man altes Delphi nutzt), kann man z.B. mit Exists oder TryGet prüfen

* wenn die Ausgabedateien bereits existieren und nicht vollständig "neu" überschrieben werden
dann deren Inhalt vorher in die jeweilige TStirngList laden

* beim "Umkopieren" der Strings in die neue Datei (TStringList) wir kein weiterer Arbeitsspeicher benötigt (da Strings mit Referenzzählung)

* tja, und am Ende dann alle Ausgabedateien "einmal" speichern, wenn vorhanden ( <>nil ), bzw. wenn Count=0



PS: Man kann auch die Eingangsdatei (TStringList) nach dem Laden einmal sortieren,
dann lässt sich anschließend jede Ausgabedatei "nacheinander" da rausholen und speichern (ein Buchstabe nach dem Anderen, wobei jeweils nur eine Ausgabedatei gleichzeitig nötig ist),
da bereits alle jeweiligen Anfangsbuchstaben zusammenhängend nacheinander in der Liste liegen.



Und wie schon beim Vorredner genannt:
Sequentiell gelesen und gespeichert (Streams oder die uralten Pascal-Dateifunktionen statt TStringList), aber alle Ausgabedateien offen lassen ... etwa gleich schnell und aufwändig, aber mit ein bissl weniger Arbeitsspeicherverbrauch.

Delphi.Narium 11. Aug 2022 14:23

AW: Große Textdatei - einzelne Zeile löschen
 
Mal so JustForFun eine Variante mit FileStream und den alten Dateifunktionen aus Pascalzeiten.
Delphi-Quellcode:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, IniFiles;

type
  rFile = record
    AFile    : TextFile;
    AFileName : String;
  end;

  TFiles = Array[32..255] of rFile;

type
  TForm1 = class(TForm)
    btnOpen: TButton;
    btnDoIt: TButton;
    btnCancel: TButton;
    btnClose: TButton;
    btnEnde: TButton;
    stb: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
    procedure btnDoItClick(Sender: TObject);
    procedure btnCancelClick(Sender: TObject);
    procedure btnCloseClick(Sender: TObject);
    procedure btnEndeClick(Sender: TObject);
  private
    { Private-Deklarationen }
    fInputFile  : String;
    fOutputFiles : String;
    fPosition   : Int64;
    fFiles      : TFiles;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// Das sollte man sinnvollerweise in eine INI-Datei schreiben.
const
  csInputFile  = 'I:\rockyou2021.txt';
  csOutputFiles = 'I:\rockyou2021-%.3d.txt';
  ciPosition   = 0;

procedure TForm1.FormCreate(Sender: TObject);
var
  Ini : TIniFile;
begin
  Ini         := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
  fInputFile  := Ini.ReadString('Config','InputFile',csInputFile);
  fOutputFiles := Ini.ReadString('Config','OutputFiles',csOutputFiles);
  fPosition   := Ini.ReadInteger('Config','Position',ciPosition);
  Ini.Free;
end;

procedure TForm1.btnOpenClick(Sender: TObject);
var
  i : Integer;
begin
  Screen.Cursor := crHourGlass;
  if fInputFile = '' then fInputFile := csInputFile;
  if fOutputFiles = '' then fOutputFiles := csOutputFiles;
  if fPosition   < 0  then fPosition := ciPosition;
  for i := Low(fFiles) to High(fFiles) do begin
    fFiles[i].AFileName := Format(fOutputFiles,[i]);
    AssignFile(fFiles[i].AFile,fFiles[i].AFileName);
    case FileExists(fFiles[i].AFileName) of
      true : Append(fFiles[i].AFile);
      false : ReWrite(fFiles[i].AFile);
    end;
  end;
  btnDoIt.SetFocus;
  Screen.Cursor := crDefault;
end;

procedure TForm1.btnDoItClick(Sender: TObject);
var
  input     : TFileStream;
  ch        : Char;
  sZeile    : String;
  iZeilen   : Integer;
  dtStart   : TDateTime;
  dtEnde    : TDateTime;
  dtLaufzeit : TDateTime;
  dProzent  : Double;
begin
  Screen.Cursor := crHourGlass;
  btnCancel.SetFocus;
  btnCancel.Tag := 0;
  dtStart      := Now;
  sZeile       := '';
  iZeilen      := 0;
  input        := TFileStream.Create(fInputFile,fmOpenRead);
  input.Seek(fPosition, soFromBeginning);
  if input.Read(ch, 1) > 0 then begin
    repeat
      case ch of
        #10 : begin
                if sZeile <> '' then WriteLn(fFiles[Ord(sZeile[1])].AFile,sZeile);
                sZeile := '';
                iZeilen := iZeilen + 1;
                if iZeilen mod 10000 = 0 then begin
                  dProzent      := input.Position * 100 / input.Size;
                  dtLaufzeit    := Now - dtStart;
                  dtEnde        := dtStart + (dtLaufzeit * 100 / dProzent);
                  stb.SimpleText := Format('Zeilen: %d - Position: %d von %d (%.2f%% - Ende ca.: %s)',
                                    [iZeilen,input.Position,input.Size,dProzent,DateTimeToStr(dtEnde)]);
                  Application.ProcessMessages;
                  if btnCancel.Tag <> 0 then break;
                end;
              end;
        #13 : begin end;
      else
        sZeile := sZeile + ch;
      end;
    until (input.Read(ch, 1) = 0);
  end;
  fPosition    := input.Position;
  dtEnde       := Now - dtStart;
  Screen.Cursor := crDefault;
  ShowMessage(Format('Zeilen: %d%sLaufzeit: %s%sPosition: %d',
              [iZeilen,sLineBreak,TimeToStr(dtEnde),sLineBreak,input.Position]));
  input.Free;
  btnClose.SetFocus;
end;

procedure TForm1.btnCancelClick(Sender: TObject);
begin
  btnCancel.Tag := 1;
end;

procedure TForm1.btnCloseClick(Sender: TObject);
var
  i : Integer;
begin
  Screen.Cursor := crHourGlass;
  for i := Low(fFiles) to High(fFiles) do CloseFile(fFiles[i].AFile);
  btnEnde.SetFocus;
  Screen.Cursor := crDefault;
end;

procedure TForm1.btnEndeClick(Sender: TObject);
var
  Ini : TIniFile;
begin
  Ini := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
  Ini.WriteString('Config','InputFile',fInputFile);
  Ini.WriteString('Config','OutputFiles',fOutputFiles);
  Ini.WriteInteger('Config','Position',fPosition);
  Ini.Free;
  Close;
end;

end.
Inidatei dazu:
Code:
[Config]
InputFile=I:\rockyou2021.txt
OutputFiles=I:\rockyou2021-%.3d.txt
Position=0
Damit sollte es möglich sein, die Verarbeitung und Aufteilung einer Datei zu starten, beliebig zu unterbrechen und zu einem späteren Zeitpunkt an der Stelle zur Weiterverarbeitung aufzusetzen, an der die vorherige Verarbeitung abgebrochen wurde.

Testdaten:

Dateigröße: 199.526.693 Byte
Zeilen : 3.225.774
Laufzeit : 00:18:11

Festplatte: extern USB 2
Rechner : Dell Optiplex GX620
PentiumR 2 x 2,80 GHZ, 1GB RAM

Hochgerechnet auf 97 GB: ca. 6 bis 7 Tage Laufzeit

Bei einem zeitgemäßen System könnte die Laufzeit durchaus kürzer ausfallen ;-)

GummiKuh68 14. Aug 2022 12:23

AW: Große Textdatei - einzelne Zeile löschen
 
Moin zusammen,

da ist man mal 3 Tage wettertechnisch (DG!) ausser Gefecht gesetzt....! :)
Mit der Resonanz hätte ich jetzt nicht gerechnet!!!

Aber eins nach dem anderen.

@Rollo62
Das Komplexe wollte ich weitestgehenst vermeiden - komplex heißt i.d.R. schwer zu Warten!

@himitsu
Ich benutze ein altes Delphi, da ich noch alte Programme von Kunden unterstützen muss,
die kein Geld für große Neuentwicklungen haben bzw. wollen dass alles ohne große Änderungen weiterläuft.
Und was den Speicherverbrauch angeht, so versuche ich diesen auf ein Minimum zu reduzieren, deshalb der
eingeschlagene Weg...der offensichtlich nicht das Optimum zu sein scheint.

@jaenicke
Danke für den Tip, muss ich mir aber auch erstmal anschauen.

@Günther
Nein, ist noch nicht zu spät. Und ja, ich mache die Zieldatei immer wieder auf und zu. Ich weiß das das Zeit kostet - deswegen ja der Thread.

@Monday
Kannte ich noch nicht. Habe aber in einem Test 3,65 GB gut mit Notepad++ öffnen können(Erklärung am Ende).

@dummzeuch
Das klingt interessant - werd ich mir mal anschauen!

@freimatz
Wie gesagt, ich hatte bei Ultraedit alles eingetragen für die Demo und auf Submit geklickt. Aber der Download wurde nie gestartet.

@Delphi.Narium
Tatsächlich handelt es sich um ASCII-11/-12 (VT,FF), ASCII-33 bis (vermutlich) ASCII-126.
Die Dateien vorher anzulegen, wäre natürlich eine Option, die bei diesem Umfang einiges bringt.
Ich sage mir aber auch - ich hab ein Programm und ich habe eine Datei die ich damit bearbeiten will.
Soll heißen, ich schmeiße dem Programm eine Datei hin und das Programm schmeißt mir mein Wunschergebnis
vor die Füße - macht also alles alleine, ohne Vorarbeit.
Dein Beispiel muss ich mir mal in Ruhe anschauen, sieht auf jeden Fall interessant aus.

Ich war natürlich - trotz der gefühlten 500°C in der Wohnung - nicht untätig und hab das Programm mal so laufen lassen.
Die erste große Datei mit 3,65 GB dauerte ewig - und das war erst die erste von vielen!
Dabei fiel mir auf, dass Delphi 7 unter Win10 nur einen Kern benutzt - hätte aber 12 auf dem Entwicklungsrechner.

Also erneut Google bemüht mal was anderes vorzuschlagen und da kam öfter Lazarus ins Spiel.
Ergo Lazarus angesehen und festgestellt, dass man das auch unter Linux nutzen kann.
Also den Pi4 angeschmissen und draufgebügelt, Projekt rübergezogen und kompiliert - schlimmer kann es ja
nicht mehr werden mit der Performance!

Was soll ich sagen - der kleine Pi4 rechnet mit allen Kernen und dadurch natürlich schneller.
Die 3,65 GB hat er nicht wie Delphi in knapp 10 Stunden geschrieben, sondern in 2!
Ich lass das jetzt so laufen (nach 2 Tagen die Hälfte geschafft), weil man den Pi dank leisem Lüfter durchlaufen lassen kann,
suche aber dennoch nach dem Weg, wie man es unter Windows schneller hinbekommt.
Den Pi4 zu benutzen, war ja eher eine Verzweifelungstat als eine Lösung! ;)

Ich schau mir jetzt erstmal die Links/den Code von euch an und hoffe, dass ich die Tage daran weiterarbeiten kann - Urlaub ist leider zu Ende.
To be continued...

Jumpy 15. Aug 2022 07:39

AW: Große Textdatei - einzelne Zeile löschen
 
Bzgl. der vorab angelegten Dateien für jeden Buchstaben: Man könnte auch sowas wie das Analogon zu Lazy Loading / Lazy Initialisation vorsehen. Du gibst deine Zeile und den ersten Buchstaben an die Funktion, die das speichern übernehmen soll. Diese hat ein Array oder Liste von allen bereits geöffneten Dateien mit dem ersten Buchstaben als Key, um die Datei in der Liste zu finden und wenn es für den Key noch keine Datei gibt, wird sie einmalig angelegt und geöffnet.

dummzeuch 15. Aug 2022 08:18

AW: Große Textdatei - einzelne Zeile löschen
 
Zitat:

Zitat von GummiKuh68 (Beitrag 1510112)
Dabei fiel mir auf, dass Delphi 7 unter Win10 nur einen Kern benutzt - hätte aber 12 auf dem Entwicklungsrechner.

Ergo Lazarus angesehen und festgestellt, dass man das auch unter Linux nutzen kann.
Also den Pi4 angeschmissen und draufgebügelt, Projekt rübergezogen und kompiliert - schlimmer kann es ja nicht mehr werden mit der Performance!

Was soll ich sagen - der kleine Pi4 rechnet mit allen Kernen und dadurch natürlich schneller.

Ich kann nicht wirklich glauben, dass derselbe Code, der mit Delphi 7 compiliert auf einem Kern läuft, mit Lazarus compiliert plötzlich alle Kerne benutzt. Da musst Du noch mehr geändert haben.

GummiKuh68 15. Aug 2022 21:24

AW: Große Textdatei - einzelne Zeile löschen
 
Liste der Anhänge anzeigen (Anzahl: 1)
...sorry, mein Fehler!
Falsch geguckt!
War nur irritiert, weil der Prozess in der Tabelle mit nur 10 - 12 % CPU-Zeit angezeigt wurde.
Wenn man sich das pro Kern ansieht, sieht das schon anders aus (s.Anhang).

Dennoch wunderts mich ein wenig, warum der kleine Pi mit allen Kernen rechnet und mit seinen 4x1,5 GHz einen 6x4GHz nass macht!?

Kann doch nicht nur ne Frage von CPU-Architektur und/oder OS-Overhead sein...!?

himitsu 15. Aug 2022 21:46

AW: Große Textdatei - einzelne Zeile löschen
 
nass macht?

rechnet er wirklich mehr/schneller, oder schiebt er nur regelmäßig den Prozess zwischen den Kernen hin und her, anstatt es auf einem kern zu lassen.


Effektiv macht es keinen großen Unterschied, ob man einen Threat 20 Zyklen auf einem Kern rechnen lässt, oder nacheinander 20 Zyklen auf unterschiedlichen Kernen.
Auf einem Kern wird es nur optimaler, wenn man den Thread zwischen den Zyklen im Kern belassen kann, ohne dass sein Context ausgelagert werden muß. (heißt, er ist auf diesem Kern nahezu alleine)



Bei Singlethread macht es aber einen Unterschied, ob man 8 Kerne a 2 GHz hat, oder 16 Kerne a 1 GHz.
(bei dem Kernewahn mancher Hersteller denkt man es wird besser, je mehr, aber wenn dafür die Kerne jeweils weniger können, dann muß es nicht immer besser sein)



Ich arbeite teilweise mit 63 Kernen ... die knapp 1-2% im Prozess sehen nach nichts aus, aber dennoch ist ein Kern zu 100% ausgelastet und es geht garnicht schneller. (außer man bekommt ein gutes Multithread hin)

jaenicke 16. Aug 2022 07:47

AW: Große Textdatei - einzelne Zeile löschen
 
Vielleicht hast du in deinem PC ja noch eine Festplatte statt einer SSD? Dann wäre klar, dass der Pi mit Flashspeicher Vorteile hat...

GummiKuh68 16. Aug 2022 09:19

AW: Große Textdatei - einzelne Zeile löschen
 
@himitsu
naja, wie schon gesagt die 3,65 GB Datei hat auf dem Desktop knapp 10 Stunden gebraucht, auf dem Pi 2!
Ich würde sagen der Pi ist da minimal schneller!

@jaenicke
Ich hab Tatsache noch ne HDD drin, dient aber nur als Ablage für Backups.

himitsu 16. Aug 2022 11:03

AW: Große Textdatei - einzelne Zeile löschen
 
Es kommt auch nicht nur auf die Rechenleistung an, denn "gerechnet" wird hier ja fast nichts.
Speicherzugriffe, Caching und Co. sind bei diesem Thema die Hauptaufgabe.

Und auf dem Pi werden bestimmt auch weniger andere Prozesse parallel arbeiten, als wie im Windows.

Rollo62 16. Aug 2022 12:34

AW: Große Textdatei - einzelne Zeile löschen
 
Ich bin immer noch der Meinung das ein "Buffering" sich lohnen würde.
Vieleicht hilft ja schon das TBufferedFileStream ?

himitsu 16. Aug 2022 12:38

AW: Große Textdatei - einzelne Zeile löschen
 
Ein Buffering kann es aber auch verschlimmern.

siehe das zu "winzige" Buffering der alten Textdatei-APIs in Delphi (AssignFile),
welches mehr gegen, als für Flashspeicher und das Buffering des OS, kämpft.

Rollo62 16. Aug 2022 12:40

AW: Große Textdatei - einzelne Zeile löschen
 
Ja aber in dem Fall hier, zeilenweises Einlesen von 4GB ...
Da würde ich 64K Buffer einlesen und Zeilenweises Lesen im Specher vorziehen.

himitsu 16. Aug 2022 13:41

AW: Große Textdatei - einzelne Zeile löschen
 
jo, midestens 16 bis 64 KB wäre schon gut.

AssignFile hat standardmäßig 128 Byte, was ja zu überhaupt nichst passt. :freak:

512 Byte ist ja die kleinste Einheit (Sektor), für Dateizugriffe, und Cluster sind auch mindestens 4 KB groß ... bis 32 KB oder gar 64 KB bei broßen Festplatten.
Und 64 KB (eigentlich 8 KB) ist die Verwaltungsgröße im Arbeitsspeicher.

Die Verwaltungsgröße des WindowsFileCache wird vermutlich auch in mehereren Cluster-Größen arbeiten.
In Delphi ist der nahezu immer mit dazwischen, da der FileCache praktisch nirgendwo deaktiviert wird. (Parameter ans CreateFile)

mytbo 16. Aug 2022 14:32

AW: Große Textdatei - einzelne Zeile löschen
 
Vermutlich habe ich die Aufgabenstellung nicht richtig verstanden. Die hier berichteten Laufzeiten lassen mich daran zweifeln. Bei meiner Lösung bin ich für 1GB Rohdaten auf eine Laufzeit von 2 Sekunden gekommen. Der gesamte Scan für ca. 100GB sollte in weniger als 4 Minuten zu schaffen sein. Die erzielte Laufzeit entspricht damit meiner Erwartung für einen ersten Entwurf. Der Quelltext ist ein Proof of Concept, weder getestet noch optimiert.
Delphi-Quellcode:
uses
  Windows, Messages, SysUtils, Variants, Classes, Contnrs,
  mormot.core.base,
  mormot.core.text,
  mormot.core.test,
  mormot.core.perf,
  mormot.core.os;

type
  TOnScanProgressEvent = procedure(pmFileSize, pmDoneSize: Int64) of Object;

  TPartFile = class(TObject)
  private
    FPartFile: TFileName;
    FTextWriter: TTextWriter;
  public
    constructor Create(const pmcPartFile: TFileName); reintroduce;
    destructor Destroy; override;
    procedure AddLine(const pmcLine: RawUtf8);
  end;

  TMapIndex = array[Byte] of Integer;

  TFileParts = class(TObject)
  private
    FFileIndex: TMapIndex;
    FFileParts: TObjectList;
    FSourceFile: TFileName;
    FBasePartFileExt: String;
    FBasePartFileName: TFileName;
    FBasePartFilePath: TFileName;
    FOnScanProgress: TOnScanProgressEvent;
    function FindFileIndex(const pmcLine: RawUtf8): Integer;
    function AddNewPartFileItem(const pmcLine: RawUtf8): Integer;
    procedure ScannedLine(const pmcLine: RawUtf8);
  public
    constructor Create(const pmcSourceFile: TFileName); reintroduce;
    destructor Destroy; override;
    procedure ProcessFile;
    property OnScanProgress: TOnScanProgressEvent
      read FOnScanProgress write FOnScanProgress;
  end;


//==============================================================================
// TPartFile
//==============================================================================

constructor TPartFile.Create(const pmcPartFile: TFileName);
begin
  inherited Create;
  FPartFile := pmcPartFile;
  FTextWriter := TTextWriter.CreateOwnedFileStream(pmcPartFile);
end;

destructor TPartFile.Destroy;
begin
  FTextWriter.FlushFinal;
  FTextWriter.Free;
  inherited Destroy;
end;

procedure TPartFile.AddLine(const pmcLine: RawUtf8);
begin
  FTextWriter.AddString(pmcLine);
  FTextWriter.AddCR;
end;

//==============================================================================
// TFileParts
//==============================================================================

constructor TFileParts.Create(const pmcSourceFile: TFileName);
begin
  inherited Create;
  FFileParts := TObjectList.Create(True);
  FillChar(FFileIndex, SizeOf(FFileIndex), -1);
  FSourceFile := pmcSourceFile;
  FBasePartFileExt := ExtractFileExt(pmcSourceFile);
  FBasePartFilePath := ExtractFilePath(pmcSourceFile);
  FBasePartFileName := Utf8ToString(GetFileNameWithoutExtOrPath(pmcSourceFile));
end;

destructor TFileParts.Destroy;
begin
  FFileParts.Free;
  inherited Destroy;
end;

function TFileParts.FindFileIndex(const pmcLine: RawUtf8): Integer;
begin
  if pmcLine <> '' then
    Result := FFileIndex[Ord(pmcLine[1])]
  else
    Result := -1;
end;

function TFileParts.AddNewPartFileItem(const pmcLine: RawUtf8): Integer;
const
  FORMAT_PARTFILE = '%s-%.3d%s';
var
  fileOrd: Integer;
  fileName: TFileName;
begin
  Result := -1;
  if pmcLine <> '' then
  begin
    fileOrd := Ord(pmcLine[1]);
    fileName := MakePath([FBasePartFilePath, Format(FORMAT_PARTFILE, [FBasePartFileName, fileOrd, FBasePartFileExt])]);
    Result := FFileParts.Add(TPartFile.Create(fileName));
    FFileIndex[fileOrd] := Result;
  end;
end;

procedure TFileParts.ScannedLine(const pmcLine: RawUtf8);
var
  idx: Integer;
begin
  if pmcLine <> '' then
  begin
    idx := FindFileIndex(pmcLine);
    if idx < 0 then
      idx := AddNewPartFileItem(pmcLine);

    if idx >= 0 then
      TPartFile(FFileParts.Items[idx]).AddLine(pmcLine);
  end;
end;

procedure TFileParts.ProcessFile;
const
  BUFFER_SIZE = 1 shl 20;
  BUFFER_ENDCHUNK = 1 shl 10;
var
  fileHnd: THandle;
  filePos: Int64;
  fileSize: Int64;
  readLine: RawUtf8;
  readCount: Integer;
  readBuffer: RawByteString;
  p, pStart: PUtf8Char;
begin
  fileHnd := FileOpenSequentialRead(FSourceFile);
  if ValidHandle(fileHnd) then
  try
    fileSize := mormot.core.os.FileSize(fileHnd);

    filePos := 0;
    readCount := 0;
    FastSetRawByteString(readBuffer, Nil, BUFFER_SIZE);
    repeat
      Inc(filePos, readCount);
      if Assigned(FOnScanProgress) then
        FOnScanProgress(fileSize, filePos);

      FileSeek64(fileHnd, filePos, soFromBeginning);
      readCount := FileRead(fileHnd, Pointer(readBuffer)^, Length(readBuffer));
      if readCount <= 0 then
        Exit; //=>

      if readCount < BUFFER_SIZE then
        FakeLength(readBuffer, readCount);

      readCount := 0;
      p := PUtf8Char(Pointer(readBuffer));
      while p <> Nil do
      begin
        pStart := p;
        readLine := GetNextLine(p, p);
        if readLine <> '' then
          ScannedLine(readLine);

        Inc(readCount, (p - pStart));
        if (BUFFER_SIZE - readCount) < BUFFER_ENDCHUNK then
          Break; //->
      end;
    until False;
  finally
    FileClose(fileHnd);
  end;
end;
Testdaten erzeugt wie folgt:
Delphi-Quellcode:
const
  ITEM_COUNT = 50000000;
var
  i: Integer;
  value: RawUtf8;
  textWriter: TTextWriter;
begin
  i := 0;
  textWriter := TTextWriter.CreateOwnedFileStream(MakePath([Executable.ProgramFilePath, 'random.data']));
  try
    while i < ITEM_COUNT do
    begin
      value := TSynTestCase.RandomIdentifier(12 + Random(20));
      if value[1] <> '_' then
      begin
        textWriter.AddString(value);
        textWriter.AddCR;
        Inc(i);
      end;
    end;

    textWriter.FlushFinal;
  finally
    textWriter.Free;
  end;
end;
Die Anwendung wie folgt:
Delphi-Quellcode:
var
  test: TFileParts;
  timer: TPrecisionTimer;
begin
  test := TFileParts.Create(MakePath([Executable.ProgramFilePath, 'random.data']));
  try
    timer.Start;
    test.ProcessFile;
    ShowMessage(Format('Total time: %s', [timer.Stop]))
  finally
    test.Free;
  end;
Der Quelltext sollte mit Delphi 7 kompatibel sein. mORMot ist bei mir immer mit dabei. Und jetzt ab ins Schwimmbad.

Nachtrag: Das Benchmark-Test-Programm ist auf einem Rechner mit SATA SSD gelaufen. Die theoretischen Transferraten sind: Lesegeschwindigkeit 550 MB/s, Schreibgeschwindigkeit 520 MB/s. Am Ergebnis sieht man, dass der limitierende Faktor für die Verarbeitung die Geschwindigkeit der SSD ist. Sie erreicht praktisch das maximal Mögliche. Die verarbeitende CPU wird mit ca. 50% belastet. Der benötigte Arbeitsspeicher ist vernachlässigbar. Vermutlich wird sich die Verteilung auch bei einer superschnellen SSD mit NVMe Anschluss kaum ändern. Die Geschwindigkeit des Datenspeichers bleibt der bestimmende Faktor. Eine weitere Optimierung macht in diesem Fall keinen Sinn. Ein kleines Schmankerl am Rande: Im Testszenario sieht man auch, dass eine SSD beides (nicht immer) kann, annähernd maximal Lesen und Schreiben gleichzeitig.

Bis bald...
Thomas


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:53 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