![]() |
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:
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.
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; 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:
...bekomme ich zurecht einen Fehler, da die Textdatei mit Reset() nur für das Lesen geöffnet wird.
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); 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!? |
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. |
AW: Große Textdatei - einzelne Zeile löschen
Zitat:
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 |
AW: Große Textdatei - einzelne Zeile löschen
Check doch erstmal, ob das Lesen der Datei der Bottleneck ist oder das Schreiben der Einzeldateien.
|
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?
|
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: ![]() Flamefire hat sich das angeschaut und das ganze mit Streams umgesetzt: ![]() Für die beste Performance musst du da vielleicht noch ein wenig schrauben, aber zumindest siehst du dort wie du mit MMFs arbeiten kannst. |
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.
|
AW: Große Textdatei - einzelne Zeile löschen
Ich hatte mal
![]() 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. |
AW: Große Textdatei - einzelne Zeile löschen
Zitat:
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. |
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.
|
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:
einsparen.
if FileExists(OFile) then
begin assignFile (OutFile, OFile); append(OutFile); end else 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; |
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 ![]() * 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. |
AW: Große Textdatei - einzelne Zeile löschen
Mal so JustForFun eine Variante mit FileStream und den alten Dateifunktionen aus Pascalzeiten.
Delphi-Quellcode:
Inidatei dazu:
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.
Code:
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.
[Config]
InputFile=I:\rockyou2021.txt OutputFiles=I:\rockyou2021-%.3d.txt Position=0 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 ;-) |
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... |
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.
|
AW: Große Textdatei - einzelne Zeile löschen
Zitat:
|
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...!? |
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) |
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...
|
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. |
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. |
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 ![]() |
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. |
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. |
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) |
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:
Testdaten erzeugt wie folgt:
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;
Delphi-Quellcode:
Die Anwendung wie folgt:
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;
Delphi-Quellcode:
Der Quelltext sollte mit Delphi 7 kompatibel sein. mORMot ist bei mir immer mit dabei. Und jetzt ab ins Schwimmbad.
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; 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 20:19 Uhr. |
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-2025 by Thomas Breitkreuz