Textdateien partiell einlesen

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
ArchChem
Beiträge: 83
Registriert: Mo 11. Jul 2022, 10:41

Textdateien partiell einlesen

Beitrag von ArchChem »

Hallo,

für eine Datenanalyse möchte ich sehr viele und teilweise sehr große (mehrere hundert MB) Textdateien einlesen, wobei ich nur an den ersten paar Headerzeilen (welche jeweils mit einem # beginnen) interessiert bin. Der eigentliche Inhalt soll nicht in den Arbeitsspeicher gelangen.
Gibt es hierfür eine ressourcensparende Möglichkeit, die Textdatei zeilenweise einzulesen und ab einem bestimmten Punkt das Einlesen zu beenden?

Viele Grüße und Danke!

Mathias
Beiträge: 6210
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Textdateien partiell einlesen

Beitrag von Mathias »

Ich würde dies mit dem klassischen ReadLn lösen. Damit Zeile für Zeile einlesen und welche gebraucht werden, in eine StringList legen.
Die anderen Zeilen dabei einfach ignorieren.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot


Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 338
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon (Windows wenn notwendig), Lazarus 3.0 FPC 3.3.1

Re: Textdateien partiell einlesen

Beitrag von Niesi »

ArchChem hat geschrieben:
Sa 28. Okt 2023, 15:32
Hallo,

für eine Datenanalyse möchte ich sehr viele und teilweise sehr große (mehrere hundert MB) Textdateien einlesen, wobei ich nur an den ersten paar Headerzeilen (welche jeweils mit einem # beginnen) interessiert bin. Der eigentliche Inhalt soll nicht in den Arbeitsspeicher gelangen.
Gibt es hierfür eine ressourcensparende Möglichkeit, die Textdatei zeilenweise einzulesen und ab einem bestimmten Punkt das Einlesen zu beenden?

Viele Grüße und Danke!
Das kommt darauf an, wie die Texte strukturiert sind. Wenn die Zeilen, welche mit einem "#" beginnen, IMMER am Anfang stehen, dann liest Du einfach Zeile für Zeile und brichst das Einlesen ab, wenn kein "#" am Zeilenanfang steht.
Wenn die aber auch verstreut im Text vorkommen, dann wirst Du alles einlesen müssen. Das würde ich mit tStringList machen und dann die benötigten Zeilen herausfiltern.
Ich bin allerdings unsicher, ob das zeilenweise Einlesen schneller ist als ein MyStringList.LoadFromFile(...);
Das solltest Du testen ...
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Textdateien partiell einlesen

Beitrag von siro »

Zur Info:

Ich wollte mal eine .csv Datei (das ist ja reiner Text) einlesen
allerdings hatte ich 10 Millionen Zeilen.

Mit ReadLn dauerte das eine knappe Stunde :shock:

Das Einlesen in eine Stringliste dauerte nur 1,2 Sekunden

Code: Alles auswählen

var tf:TStringlist;
begin
  tf:=TStringlist.create;
  tf.LoadFromFile(filename);
{zugriff dann z.B. über:}
  Writeln(tf.strings[i]);
{zum schluss dann noch ein}
  tf.free;
end;
Das Zuordnen an eine ListBox dauerte wieder ne Stunde....
ListBox.Items.Assign(tf)
ebenso wie
ListBox.Items:=tf;

Das Einlesen der kompletten Datei mittels Blockread benötigte lediglich 60ms (SSD-Festplatte)
Dann hat man halt alles in einem Stück im Speicher und muss natürlich die Daten/Zeilen selbst irgendwie sortieren und verarbeiten.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Joh
Lazarusforum e. V.
Beiträge: 191
Registriert: Sa 26. Mai 2012, 17:31
OS, Lazarus, FPC: Win 10 (L 2.2.6 x64 FPC 3.2.2)
CPU-Target: 64Bit

Re: Textdateien partiell einlesen

Beitrag von Joh »

ArchChem hat geschrieben:
Sa 28. Okt 2023, 15:32
Der eigentliche Inhalt soll nicht in den Arbeitsspeicher gelangen.
war eine der Anforderungen.
Also fällt LoadFromFile schon raus.

Daher klassisch ReadLn mit repeat until...
just my two Beer

Benutzeravatar
Jorg3000
Lazarusforum e. V.
Beiträge: 170
Registriert: So 10. Okt 2021, 10:24
OS, Lazarus, FPC: Win64
Wohnort: NRW

Re: Textdateien partiell einlesen

Beitrag von Jorg3000 »

Moin!
Man kann das Lesen aus einer Textdatei erheblich beschleunigen, indem man einen Textbuffer zuweist.
Mein folgendes Beispiel weist 60 kb Buffer zu, während der Standardpuffer nur lächerliche 255 Byte beträgt, glaube ich.

Code: Alles auswählen

type TBuf = packed Array[0..60*1024-1] of Byte;  // 60 KB
var f: TextFile;
    Buf: ^TBuf;                     
begin
  AssignFile(f,...);
  Reset(f);
  Buf:=nil;
  New(Buf);
  System.SetTextBuf(f,Buf^,SizeOf(TBuf));
  Repeat ReadLn(...) Until ...;
  CloseFile(f);
  Dispose(Buf); 
end;
Vor vielen Jahren habe ich mal einen Geschwindigkeitstest mit verschiedenen Buffergrößen gemacht. Dabei kam auf meinem damaligen PC raus, dass bei Buffergrößen über 100 kb das Einlesen wieder langsamer wurde. Deshalb hatte ich damals 60 kb gewählt.

Den Puffer habe ich absichtlich umständlich über einen Zeiger mit New/Dispose angelegt, damit der große Puffer nicht im Stack, sondern im Heap liegt, um einen Stack-Überlauf zu vermeiden.
Grüße, Jörg

Mathias
Beiträge: 6210
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Textdateien partiell einlesen

Beitrag von Mathias »

Man kann das Lesen aus einer Textdatei erheblich beschleunigen, indem man einen Textbuffer zuweist.
Mein folgendes Beispiel weist 60 kb Buffer zu, während der Standardpuffer nur lächerliche 255 Byte beträgt, glaube ich.
Dies kannte ich nicht. Read und ReadLn sind schon recht gemütlich Wobei Read noch extremer ist..
Gab es dies schon zur TP Zeiten ?

Dies mit New und Dispose ist auch elegant gelöst. Dies kannte ich eher von Objecten. ZB. in Freevision.
Ich habe immer Getmem und Freemem verwendet.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Textdateien partiell einlesen

Beitrag von siro »

Grade gefunden:
https://www.freepascal.org/docs-html/rt ... xtbuf.html

Der Standard Puffer ist wohl 128 Bytes
Der maximale Puffer angeblich nur 65355 Bytes ???? komischer Wert, evtl. doch 65535 ????

Das spricht für die Erfahrung von Jörg, dass größere Puffer dann nix mehr bringen.

Ich habe jetzt mal einige Versuche gestartet, wobei ich den Puffer auch noch kleiner gemacht habe,
sofern das System das überhaupt übernimmt, das weis man ja nicht.
Auch habe ich GetMem und FreMem sowie New und Dispose probiert.

Mein Testobjekt ist eine .CSV Datei mit 107 MByte und 2,1 Millionen Zeilen

GetMem + FreeMem
Buffer 32 Bytes Zeit 5,39 Sekunden
Buffer 128 Bytes Zeit 5,42 Sekunden
Buffer 1024 Bytes Zeit 5,47 Sekunden
Buffer 4096 Bytes Zeit 5,41 Sekunden
Buffer 16384 Bytes Zeit 5,40 Sekunden
Buffer 65355 Bytes Zeit 5,43 Sekunden, angeblich die maximale Grösse laut Doku
Buffer 10000000 Bytes Zeit 5,44 Sekunden

nun das ganze nochmal mit
New und Dispose:
Buffer 32 Bytes Zeit 5,40 Sekunden
Buffer 128 Bytes Zeit 5,42 Sekunden
Buffer 1024 Bytes Zeit 5,42 Sekunden
Buffer 4096 Bytes Zeit 5,41 Sekunden
Buffer 16384 Bytes Zeit 5,40 Sekunden
Buffer 65355 Bytes Zeit 5,44 Sekunden, angeblich die maximale Grösse laut Doku
Buffer 10000000 Bytes Zeit 5,44 Sekunden

Fazit: Egal, was ich da ein/umstelle, ich kann da keinen Geschwindigkeitszuwachs feststellen.
Das kann natürlich an dem System selbst liegen. Auch könnten da die Caches von Festplatte usw. mitspielen.
Theoretisch müsste ich vor jedem Test den Rechner neu booten....Aber das erspar ich mir jetzt.... :wink:

Einen schönen Sonntag noch.

Achja, anbei mein Testprogramm:
project1.zip
(106.41 KiB) 52-mal heruntergeladen
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Mathias
Beiträge: 6210
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Textdateien partiell einlesen

Beitrag von Mathias »

Mein Testobjekt ist eine .CSV Datei mit 107 MByte und 2,1 Millionen Zeilen
Die Unterschiede liegen fast in Messtoleranz.

Aber wen ich weiter oben von Jorg3000 hätte ich einen grösseren Unterschied erwartet.

Ich vermute mal, das es mit TStringList.LoadFiles am schnellsten geht.
Was bei ReadLn sicher der Fall ist, das es immer nach LineEnding testet. Block einlesen wieder testen usw.
Und StringList liest alles in einem Block ein und es wird vor zu in einzelne Strings gesplittet.

Ich vermute mal dies schnellste Lösung wird sein, mit SizeOf die Grösse der Datei ermitteln, mit GetMem im RAM ein gleich grossere Block anlegen.
Anschliessend mit BlockRead oder was ähnlichem direkt in den reservierten RAM laden.

Wen alles im RAM ist, dann nach dem "#" suchen.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 338
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon (Windows wenn notwendig), Lazarus 3.0 FPC 3.3.1

Re: Textdateien partiell einlesen

Beitrag von Niesi »

Ich habe auch mal rumprobiert. Das Thema ist für mich relevant, ich muss sehr oft Textdateien einlesen, auswerten und ausgeben - allerdings sind die Dateien für mich nicht so irre groß. Ich habe mir aber eine Datei zusammenkopiert, die jetzt 1,939,201 Zeilen hat.

Davon habe ich die ersten 120 Zeilen mit einem '#' als erstes Zeichen versehen.

Ich habe auf GetMem und so verzichtet. Einlesen in eine StringList liegt im Sekundenbereich, das Anzeigen in einem Memo ebenfalls. Wenn es viele Dateien auszuwerten gilt , dann ist das natürlich lästig.

Aber: Verbinden mit AssignFile und ReadLn, bis das erste Zeichen kein '#' mehr ist, benötigt Sekundenbruchteile, dann werden die 120 Zeilen angezeigt. ("assign file III" im Menü.)

Euch auch einen schönen Sonntag
Harald

...
LoadText.7z
(200.28 KiB) 49-mal heruntergeladen
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

Mathias
Beiträge: 6210
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Textdateien partiell einlesen

Beitrag von Mathias »

Ich habe mir aber eine Datei zusammenkopiert, die jetzt 1,939,201 Zeilen hat.
Bei sie riessen Dateien fragt man sich, ob diese Text sein müssen.
Da würde ich eher auf ein Binär-Format ausweichen.
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

Benutzeravatar
Niesi
Lazarusforum e. V.
Beiträge: 338
Registriert: So 26. Jun 2016, 19:44
OS, Lazarus, FPC: Linux Mint Cinnamon (Windows wenn notwendig), Lazarus 3.0 FPC 3.3.1

Re: Textdateien partiell einlesen

Beitrag von Niesi »

Nun ja, das ist halt manchmal vorgegeben.

Textdateien sind die einfachste Variante, wichtige Daten zu speichern. Im Falle eines Falles können die Daten mit einem Texteditor bearbeitet und gelesen werden - entsprechende Sachkenntnis vorausgesetzt. Bei anderen Formaten können Fehler verheerende Folgen haben ...
Wissen ist das einzige Gut, das sich vermehrt, wenn es geteilt wird ...

Benutzeravatar
Jorg3000
Lazarusforum e. V.
Beiträge: 170
Registriert: So 10. Okt 2021, 10:24
OS, Lazarus, FPC: Win64
Wohnort: NRW

Re: Textdateien partiell einlesen

Beitrag von Jorg3000 »

Moin!
siro hat geschrieben:
So 29. Okt 2023, 11:32
Fazit: Egal, was ich da ein/umstelle, ich kann da keinen Geschwindigkeitszuwachs feststellen.
Kein Wunder, denn in deinem Download-Beispiel steht das SetTextBuf() an der falschen Stelle.
Es muss direkt hinter dem Reset(F) eingebaut werden, wie in meinem obigen Beispiel-Code.

Und ob du den Buffer mit New() oder GetMem() erzeugst, spielt natürlich keine Rolle, denn intern ist das quasi das Gleiche und es wird ja nur ein einziges Mal pro Datei gemacht.

Ich habe gerade keine Zeit und keine Riesendatei zu Testen. Es würde mich sehr interessieren, ob du einen Geschwindigkeitsunterschied messen kannst, wenn du SetTextBuf() hinter Reset(F) einbaust.
Grüße, Jörg

siro
Beiträge: 732
Registriert: Di 23. Aug 2016, 14:25
OS, Lazarus, FPC: Windows 11
CPU-Target: 64Bit
Wohnort: Berlin

Re: Textdateien partiell einlesen

Beitrag von siro »

Guten Morgen,

Ui, das wusste ich garnicht, dass ich es hinter Reset setzen mus.. :shock:
Hab vielen Dank Jörg.

Ich dachte SetTextBuf wäre eine globale Einstellung :P
Wenn man aber mal bischen nachdenkt.....ich übergebe ja auch den TextFile :wink:

Nun habe ich es ausprobiert und siehe da, kaum macht mans richtig funktioniert es auch:
Hier sind die neuen Zeiten:

10 Byte 85,74 sec
50 Byte 19,24 sec
128 Byte 8,72 sec
255 Byte 5,19 sec
1024 Byte 2,58 sec
4096 Byte 1,84 sec
8192 Byte 1,70 sec
16384 Byte 1,64 sec
32768 Byte 1,60 sec
65536 Byte 1,58 sec
60 KByte 1,59 sec
100KByte 1,59 sec
1MByte 1,59 sec
10MByte 1,57 sec

Die magische Grenze scheint also bei 60 bzw. 64 KByte zu liegen

Nochmals vielen Dank.
Zuletzt geändert von siro am Mo 30. Okt 2023, 09:36, insgesamt 2-mal geändert.
Grüße von Siro
Bevor ich "C" ertragen muß, nehm ich lieber Lazarus...

Antworten