Handling von großen ASCII-Files

Für allgemeine Fragen zur Programmierung, welche nicht! direkt mit Lazarus zu tun haben.
Antworten
Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Handling von großen ASCII-Files

Beitrag von photor »

Hallo Forum,

ich brauche ein paar Anregungen zu folgendem Problem:
  • Es gibt (seit langem) ein Delphi-Programm in der Firma, das genutzt wird, um FEM-Ergebnisse weiter zu verarbeiten. Die Ergebnisdateien sind groß (bis zu mehreren GB) und es müssen Daten aus mehreren gleichzeitig verarbeitet werden. Das Handling ist momentan eher weniger optimal (es wird mehr Zeit mit dem Lesen der Daten als mit deren Verarbeitung vertan). Ich versuche das nun zu verbessern (es gibt mittlerweile ja größere, schnellere Rechner :) ).
  • Die Files sind ASCII-Files und bestehen aus verschiedenen Sektionen unterschiedlicher Größe und Aufbau (das wird "Postcode" genannt), so dass man irgendwie durch das ganze File durch muss, um die Daten (Postcodes) zusammenzutragen; meine Idee, diese Sektionen zu identifizieren, einzulesen und die Daten gleich in entsprechende Strukturen zu speichern (es werden nicht alle Sektionen gebracht).
  • Für meine Versuche in Lazarus (zuhause) habe ich erstmal platterweise so ein File als ganzes in eine TStringList eingelesen; ein solches File passte auch problemlos in meinen (Linux-)Rechner. Die TStringList konnte man dann recht einfach mittels Index durchlaufen und auswerten. Das funktioniert unter Delphi/Windows leider nicht ("not enough memory" wohl weil noch 32-bit-ig(*)) beim sl:=StringList.Create; sl.LoadFromFile(..);.
  • Deshalb versuche ich, die Files mittels TFileStream zu lesen, was prinzipiell funktioniert; ich kann das File Zeile für Zeile einlesen und diese dann weiterverarbeiten: erstmal nur nach den Postcodes suchen. Soweit funktioniert es.
  • Wenn ein Postcode gefunden ist, der ausgewertet werden soll, würde ich gerne eine Funktion damit beauftragen. Hier die Frage: kann ich den Stream an die Funktion weitergeben, so dass die dort weiter parsen kann? Und kann ich die Position im Stream am Ende zurück geben, so dass ich weiter suchen kann und von dort die nächste Postcode-Auswerteroutine aufrufen kann? Ich möchte vermeiden, jedesmal den Stream neu zu öffnen und wieder von vorne zu beginnen.
  • Gibt es noch andere Herangehensweisen? Stichworte zum Nachlesen reichen.
Wie immer, sehr dankbar für jede Anregung,

Photor

PS: wenn Codeschnipsel gewünscht werden, kann ich was extrahieren und anpassen.

(*) Umstellung auf 64 Bit wird sicher auch irgendwann kommen, ist aber für den Moment eine zu große Baustelle.

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

Re: Handling von großen ASCII-Files

Beitrag von Mathias »

Das funktioniert unter Delphi/Windows leider nicht ("not enough memory" wohl weil noch 32-bit-ig(*)) beim sl:=StringList.Create; sl.LoadFromFile(..);.

Da sagst es, bei 32Bit ist bei 4GByte fertig. Da es unter Linux läuft, hast du sehr grosse Chance, das ist mit dem 64Bit Lazarus auch unter Windows geht. Vor aus gesetzt, du hast ein 64Bit Windows.

Es gibt (seit langem) ein Delphi-Programm in der Firma, das genutzt wird, um FEM-Ergebnisse weiter zu verarbeiten. Die Ergebnisdateien sind groß (bis zu mehreren GB) und es müssen Daten aus mehreren gleichzeitig verarbeitet werden. Das Handling ist momentan eher weniger optimal (es wird mehr Zeit mit dem Lesen der Daten als mit deren Verarbeitung vertan). Ich versuche das nun zu verbessern (es gibt mittlerweile ja größere, schnellere Rechner :) ).
Ein Binär-Format wäre für diesen Zweck wohl besser gewesen. Oder man verwendet mehrere kleinere Dateien, aber das Ganz ist wohl recht schwierig, wen man etwas bestehendes ändern will. :wink:
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Handling von großen ASCII-Files

Beitrag von wp_xyz »

photor hat geschrieben:Wenn ein Postcode gefunden ist, der ausgewertet werden soll, würde ich gerne eine Funktion damit beauftragen. Hier die Frage: kann ich den Stream an die Funktion weitergeben, so dass die dort weiter parsen kann?

Natürlich. Etwa so:

Code: Alles auswählen

function FindPostCode(AStream: TStream; APostCode: String): Int64;  

photor hat geschrieben:Und kann ich die Position im Stream am Ende zurück geben, so dass ich weiter suchen kann und von dort die nächste Postcode-Auswerteroutine aufrufen kann? Ich möchte vermeiden, jedesmal den Stream neu zu öffnen und wieder von vorne zu beginnen.

Ebenfalls natürlich. Verwende die les-/schreibbare Eigenchaft "Position" des Streams. Achtung: Position wird mit jeder Schreib/Leseaktion automatisch weitergerückt.

Code: Alles auswählen

var
  w: Word;
begin
  AStream.Position := 1000;   // Setze den Stream auf "Index" 1000;
  w := AStream.ReadWord,      // Lese zwei Byte --> Position steht auf 1002

Ein Tipp noch: Einen mehrere GB großen Filestream Byte für Byte einzulesen wird ganz schön lang dauern. Ein MemoryStream scheidet aus, weil dann die Datei komplett in Speicher liegt. Nimm einen gepufferten Stream, etwa TBufStream, der allerdings, wenn ich mich nicht täusche, nur vorwärts laufen kann. Oder nimm den gleichnamigen aus fpspreadsheet, der kann vorwärts/rückwärts laufen; die Unit fpsstreams ist autark und kann ohne den Rest des Package verwendet werden (https://sourceforge.net/p/lazarus-ccr/s ... treams.pas). So ein gepufferter Stream liest die Datei häppchenweise ein (der von fpspreadsheet nimmt 1MB-Blöcke), so dass man auf diesem Dateistück im Speicher arbeitet; wenn der eingelesene Bereich überschritten wird, dann wird automatisch der nächste Block nachgeladen.

Dann noch: Als Laufvariable im Stream Int64 verwenden, mit Integer stößt du bei 2GB an die Grenzen.

photor hat geschrieben:Gibt es noch andere Herangehensweisen? Stichworte zum Nachlesen reichen.

Es gibt noch Memory-Mapped-Files. Da greift man auf Dateien so zu, als wären sie im Speicher, obwohl sie nicht im klassichen Sinn eingelesen wurden. Dazu gab es vor kurzem einen Beitrag im englischen Forum, wo auch so ein Stream vorgestellt worden ist, allerdings nur für Windows, wenn mich nicht alles täuscht (http://forum.lazarus.freepascal.org/ind ... #msg254956).

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: Handling von großen ASCII-Files

Beitrag von photor »

Mathias hat geschrieben:Da sagst es, bei 32Bit ist bei 4GByte fertig. Da es unter Linux läuft, hast du sehr grosse Chance, das ist mit dem 64Bit Lazarus auch unter Windows geht. Vor aus gesetzt, du hast ein 64Bit Windows.

Das File selbst war nur ca. 1.7 GB groß. Aber als StringList wird es wohl größer. Egal; weil irgendwann werden Files größer(*) => 32-bit ist auf Dauer keine Lösung (aber muss es zunächst bleiben wg. Zeitmangel).
Das File als ganzes in den Speicher zu laden, habe ich aufgegeben (zumal ich für die Auswertung üblicherweise Daten aus 7 und mehr solcher Files brauche - FEM bedeutet: Datenmassen bewältigen).
Allerdings ist die Umstellung auf 64-bit nicht so einfach: ein (naiver) Versuch endete mit Zusammen-klappen des Programms - irgendwo wird noch eine 32-bit-Komponente verwendet, denke ich. Aber da gibt es leider noch viiieeele andere Baustellen ...
In der Firma steht ein Delphi zur Verfügung (64-bit-Windows); Lazarus ist nur zuhause mein Testfeld.

Mathias hat geschrieben:Ein Binär-Format wäre für diesen Zweck wohl besser gewesen. Oder man verwendet mehrere kleinere Dateien, aber das Ganz ist wohl recht schwierig, wen man etwas bestehendes ändern will. :wink:

Ja. Und - Das Format ist vom FEM-Programm (kommerzielles Programmpaket) vorgegeben. Zur Eherenrettung: die bieten natürlich ein Binärformat an. Da sind die Files kleiner, aber die Daten da raus zu klauben ist bestimmt um etliches schwerer (SCHÜTTELFROSTANFALL).

Ciao,
Photor

(*)zumal noch andere Beschränkungen zuschlagen können: Stack-Größe fällt mir als erstes ein.

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: Handling von großen ASCII-Files

Beitrag von Timm Thaler »

Als erstes: Datenreduktion.

Kannst Du die Dateien umkopieren und dabei nicht relevante Daten weglassen?

Als zweites: Virtuelle Disk.

Unter Linux hab ich ein virtuelles Verzeichnis eingerichtet, in dem Dateien temporär abgelegt werden. Der Zugriff darauf ist natürlich erheblich schneller.

Erfordert allerdings entsprechenden RAM, unter 16GB braucht man da wahrscheinlich nicht anfangen. Wo wir wieder beim 64bit-System wären.

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: Handling von großen ASCII-Files

Beitrag von photor »

Timm Thaler hat geschrieben:Als erstes: Datenreduktion.
Kannst Du die Dateien umkopieren und dabei nicht relevante Daten weglassen?

Das versuche ich gerade: einmal durchscannen muss ich die Dateien dafür aber erstmal: Postcodes finden, die ich brauche, andere weglassen, einige können reduziert werden (dann müssen andere angepasst werden). Eine komplexe Materie.

Timm Thaler hat geschrieben:Als zweites: Virtuelle Disk.
[...]
Erfordert allerdings entsprechenden RAM, unter 16GB braucht man da wahrscheinlich nicht anfangen. Wo wir wieder beim 64bit-System wären.

Das Arbeitssystem ist groß genug (128 GB, Windows). Da wäre was in diese Richtung möglich: Aber das wird von der IT wohl nicht eingerichtet werden (müsste sich an- und abschalten lassen, da andere Programme massiv im RAM arbeiten).

Ich sehe in der Überarbeitung des File-Handling eher Potenzial; das packt das Problem an der Wurzel.

Ciao,
Photor

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: Handling von großen ASCII-Files

Beitrag von photor »

Hallo wp_xyz,

(sorry, hätte Dich fast vergessen)

wp_xyz hat geschrieben:
photor hat geschrieben:kann ich den Stream an die Funktion weitergeben, so dass die dort weiter parsen kann?

Natürlich. Etwa so:

Code: Alles auswählen

function FindPostCode(AStream: TStream; APostCode: String): Int64;  



wp_xyz hat geschrieben:
photor hat geschrieben:Und kann ich die Position im Stream am Ende zurück geben

Ebenfalls natürlich. Verwende die les-/schreibbare Eigenchaft "Position" des Streams. Achtung: Position wird mit jeder Schreib/Leseaktion automatisch weitergerückt.

Code: Alles auswählen

var
  w: Word;
begin
  AStream.Position := 1000;   // Setze den Stream auf "Index" 1000;
  w := AStream.ReadWord,      // Lese zwei Byte --> Position steht auf 1002

Ich habe das erstmal mit dem TFileStream gelöst. Der ist Unit-weit global, und die Funktion, die jeweils einen Postcode einliest (zeilenweise mittels str:=fl.ReadLine, die Anzahl der eingelesen Zeilen zurück gibt.

wp_xyz hat geschrieben:Ein Tipp noch: Einen mehrere GB großen Filestream Byte für Byte einzulesen wird ganz schön lang dauern. Ein MemoryStream scheidet aus, weil dann die Datei komplett in Speicher liegt. Nimm einen gepufferten Stream, etwa TBufStream, der allerdings, wenn ich mich nicht täusche, nur vorwärts laufen kann. Oder nimm den gleichnamigen aus fpspreadsheet, der kann vorwärts/rückwärts laufen; die Unit fpsstreams ist autark und kann ohne den Rest des Package verwendet werden (https://sourceforge.net/p/lazarus-ccr/s ... treams.pas). So ein gepufferter Stream liest die Datei häppchenweise ein (der von fpspreadsheet nimmt 1MB-Blöcke), so dass man auf diesem Dateistück im Speicher arbeitet; wenn der eingelesene Bereich überschritten wird, dann wird automatisch der nächste Block nachgeladen.


wp_xyz hat geschrieben:Dann noch: Als Laufvariable im Stream Int64 verwenden, mit Integer stößt du bei 2GB an die Grenzen.

Guter Hinweis. Das werde ich beherzigen (obwohl ich momentan Zeile zähle).

wp_xyz hat geschrieben:
photor hat geschrieben:Gibt es noch andere Herangehensweisen? Stichworte zum Nachlesen reichen.

Es gibt noch Memory-Mapped-Files. Da greift man auf Dateien so zu, als wären sie im Speicher, obwohl sie nicht im klassichen Sinn eingelesen wurden. Dazu gab es vor kurzem einen Beitrag im englischen Forum, wo auch so ein Stream vorgestellt worden ist, allerdings nur für Windows, wenn mich nicht alles täuscht (http://forum.lazarus.freepascal.org/ind ... #msg254956).

Den Link (und den oben) schau ich mir an.

Danke für die Tipps,
Photor

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Handling von großen ASCII-Files

Beitrag von wp_xyz »

photor hat geschrieben:Ich habe das erstmal mit dem TFileStream gelöst. Der ist Unit-weit global, und die Funktion, die jeweils einen Postcode einliest (zeilenweise mittels str:=fl.ReadLine, die Anzahl der eingelesen Zeilen zurück gibt.

Keine Ahnung, was du damit meinst. Der übliche TFilestream, so wie er in der Unit Classes implementiert ist, hat jedenfalls keine Methode Readline.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Handling von großen ASCII-Files

Beitrag von braunbär »

Hast du schon daran gedacht, in einem ersten Schritt deine Dateien in irgend ein vernünftiges SQL Datenbankformat zu konvertieren (Firebird, MySQL, SQLite, was auch immer)?
Das sollte nicht allzu aufwändig sein, und danach kannst du die Daten dank der Indexierungsmöglicheiten einer Datenbank sehr effizient selektieren und bearbeiten.

creed steiger
Beiträge: 957
Registriert: Mo 11. Sep 2006, 22:56

Re: Handling von großen ASCII-Files

Beitrag von creed steiger »

An eine Ramdisk hätte ich als erstes gedacht .. das könnte doch einiges an Geschwindigkeit bringen.
ansonsten das was wp_xyz vorschlägt

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: Handling von großen ASCII-Files

Beitrag von photor »

wp_xyz hat geschrieben:
photor hat geschrieben:Ich habe das erstmal mit dem TFileStream gelöst. Der ist Unit-weit global, und die Funktion, die jeweils einen Postcode einliest (zeilenweise mittels str:=fl.ReadLine, die Anzahl der eingelesen Zeilen zurück gibt.

Keine Ahnung, was du damit meinst. Der übliche TFilestream, so wie er in der Unit Classes implementiert ist, hat jedenfalls keine Methode Readline.


Oh. Da hast Du recht; ich war ungenau und war inzwischen beim TStreamReader gelandet; dort gibt es die Methode ReadLine:

Code: Alles auswählen

 
slT19File := TStreamReader.Create(FileName);
while not(slT19File.EndOfStream) do
  begin
    str := slT19File.ReadLine;
    inc(nl);
...
  end;
end;
 

Die Variable nl zählt einfach die gelesenen Zeilen hoch. Momentan gehe ich einfach nur linear durch das File und sammle die Daten. Ob ich die dann (gefiltert und reduziert) wieder in ein anderes File rausschreibe, hangt auch davon ab, ob und wieviel ich im Speicher halten kann (jedes File etwa 1.7 GB; im konkreten Fall brauche ich 18 solcher Files).

Ciao,
Photor

Antworten