Delphi-PRAXiS
Seite 2 von 3     12 3      

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)

Delphi.Narium 11. Aug 2022 11: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 11: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 15: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 13: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 08: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 09: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 22: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 22: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 08: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 10: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.


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:29 Uhr.
Seite 2 von 3     12 3      

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz