TFileStream, zum Öffnen sehr sehr großer Dateien..

Rund um die LCL und andere Komponenten
Antworten
Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

TFileStream, zum Öffnen sehr sehr großer Dateien..

Beitrag von corpsman »

Servus,

ich versuche gerade auf einem Raspi mehrere Hundert MB große Textdateien zu verarbeiten.

Der Naive Ansatz:

Code: Alles auswählen

 
var sl:TStringlist;
i:integer;
begin
sl := TStringlist.create;
sl.loadfromfile(..);
for i := 0 to sl.clount -1 do begin
end;
sl.free
 


Komischerweise allokiert er für eine 160MB Textfile so viel Speicher, dass mir das 1GB-RAM eigentlich nicht mehr Reicht. Da mein Algorithmus im 1 Pass Verfahren einfach ein mal die Daten durchgeht war die Idee, nicht immer das gesamte File zu laden sondern "Blockweise" durch zu gehen.

Dazu wollte ich eigentlich TFiletream nehmen, aber laut : http://wiki.freepascal.org/TFileStream/de würde das nicht funktionieren, denn dort steht:

Bei einem Filestream wird eine Datei vollständig in den Arbeitsspeicher gelesen.


Meine Persönliche Erfahrung spiegelt das nicht wieder. Mit den Ntools versende ich zum Teil Gigabyte große Dateien und das geht wunderbar auch wenn die deutlich größer sind als der Speicher den der jeweilige Rechner gerade hat.

Ist hier das Wiki falsch, oder habe ich etwas übersehen.
Und Mit welcher Variante kann ich denn nun riesige Dateien stückweise durchgehen ?
--
Just try it

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

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von wp_xyz »

corpsman hat geschrieben:S
Dazu wollte ich eigentlich TFiletream nehmen, aber laut : http://wiki.freepascal.org/TFileStream/de würde das nicht funktionieren, denn dort steht:
Bei einem Filestream wird eine Datei vollständig in den Arbeitsspeicher gelesen.


Ich denke, da ist dem wiki-Autor die Phantasie durchgegangen. Abgesehen von der Pufferung im Betriebssystem, liest ein File-Stream so viele Bytes wie man im Read*-Befehl anfordert.

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von corpsman »

Gut, irritiert hatte es mich, da es in der Englischen variante auch steht.

Da ich ASCII files Zeilenweise verarbeite habe ich gemäß hier : http://forum.lazarus.freepascal.org/ind ... ic=16402.0

mal folgendes zusammengestrickt:

Code: Alles auswählen

 
Unit uunbufstringlist;
 
{$MODE objfpc}{$H+}
 
Interface
 
Uses
  Classes, SysUtils;
 
Type
 
  { TUnbufferedStringList }
 
  TUnbufferedStringList = Class
  private
    fFilename: String;
    ffile: TextFile;
    Procedure CloseFile;
  public
    Constructor Create;
    Destructor Destroy; override;
    Function LoadFromFile(Const Filename: String): Boolean;
 
    Function EOF: Boolean;
    Function Readln(): String;
  End;
 
Implementation
 
{ TUnbufferedStringList }
 
Constructor TUnbufferedStringList.Create;
Begin
  fFilename := '';
End;
 
Destructor TUnbufferedStringList.Destroy;
Begin
  CloseFile;
  Inherited Destroy;
End;
 
Procedure TUnbufferedStringList.CloseFile;
Begin
  If fFilename <> '' Then objpas.CloseFile(ffile);
End;
 
Function TUnbufferedStringList.LoadFromFile(Const Filename: String): Boolean;
Begin
  result := false;
  CloseFile;
  fFilename := '';
  If FileExists(Filename) Then Begin
    fFilename := Filename;
    assignfile(ffile, Filename);
    reset(ffile);
    result := true;
  End;
End;
 
Function TUnbufferedStringList.EOF: Boolean;
Begin
  result := true;
  If fFilename <> '' Then Begin
    result := system.EOF(ffile);
  End;
End;
 
Function TUnbufferedStringList.Readln(): String;
Begin
  result := '';
  If fFilename <> '' Then Begin
    system.Readln(ffile, result);
  End;
End;
 
End.
 


Damit muss ich recht wenige Anpassungen von TStringlist nach TUnbufferedStringList machen und müsste mein Problem lösen können ;)

Schreiben muss ich nicht. Sieht hier jemand Optimierungsbedarf ?
Zuletzt geändert von corpsman am Mi 25. Apr 2018, 10:05, insgesamt 1-mal geändert.
--
Just try it

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von Socke »

Versuch mal:

Code: Alles auswählen

var
  sl: TStringList;
  fs: TFileStream;
  ss: TStringStream;
  i: Integer;
begin
  fs := TFileStream.Create('myfile.txt', fmOpenRead or fmShareDenyWrite);
  ss := TStringStream.Create('');
  sl := TStringList.Create;
  try
    ss.Size := fs.Size;
    ss.CopyFrom(fs, fs.Size);
    ss.Position := 0;
    sl.Text := ss.DataString;
    // daten verarbeiten
    for i := 0 to sl.clount -1 do begin
      doSomething()i;
    end;
  finally
    ss.Destroy;
    fs.Destroy;
    sl.Destroy;
  end;


TFileStream hat keinen eigenen Puffer. Problematisch ist TStrings.LoadFromStream(), das für normale Dateien den Buffer ineffizient in einer Schleife allokiert (wahrscheinlich um non-seekable Streams wie Pipes zu unterstützen).
Mit dem Ansatz oben hast brauchst du zeitweise den doppelten Speicher für eine Datei (im TStringStream und in der TStringList). Alternativ hilft hier nur TStrings.LoadFromStream() neu zu implementieren, dabe die Datei stückweise einzulesen und den Teil des Buffers bis zum letzten Zeilenumbruch mit TStrings.AddText() hinzuzufügen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von corpsman »

@socke

Deine Variante würde also auf meinem 1GB raspi Dateien bis 500MB zulassen (zumindest in der Theorie).

Mit meinem Vorschlag aus dem vorherigen Post, müsste die Dateigröße durch das Filesystem begrenzt sein, oder ?
--
Just try it

Benutzeravatar
kralle
Lazarusforum e. V.
Beiträge: 985
Registriert: Mi 17. Mär 2010, 14:50
OS, Lazarus, FPC: Linux Mint 20 , FPC 3.3.1 , Lazarus 2.1.0 -Win10 & XE7Pro
CPU-Target: 64Bit
Wohnort: Bremerhaven
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von kralle »

Moin,

corpsman hat geschrieben:Servus,
ich versuche gerade auf einem Raspi mehrere Hundert MB große Textdateien zu verarbeiten.


Was willst Du nach den Einlesen mit dem Text machen?
Wenn Du den Text mit

Code: Alles auswählen

READLN(f,zeile)
zeilenweise einliest, brauchst Du echt wenig Speicher.

Gruß Heiko
OS: Manjaro Linux, Debian und Windows 10
FPC-Version: 3.2.2 , Lazarus 3.0
+ Delphi XE7SP1

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von corpsman »

Ich sende den Text via Uart an einen 3D-Drucker ;)
--
Just try it

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von Socke »

corpsman hat geschrieben:Deine Variante würde also auf meinem 1GB raspi Dateien bis 500MB zulassen (zumindest in der Theorie).

Mit virtuellem Speicher kommst du noch weiter - die Frage ist: willst du dir eine 128 GB SD-Karte anschaffen um 30 GB große Dateien einzulesen? Die nutzbare Grenze hängt von vielen Faktoren ab, ohne grafische Oberfläche vermutlich bei einer Dateigröße von ca. 300 MB (300 MB Stringstream + 300 MB Stringlist + fester Videospeicher + restliches System)

corpsman hat geschrieben:Mit meinem Vorschlag aus dem vorherigen Post, müsste die Dateigröße durch das Filesystem begrenzt sein, oder ?

Hier liest du nur Zeilenweise ein; sofern du nicht alles wieder in eine Stringlist lädst oder anderweitig im Speicher behälst kannst du damit beliebig große Dateien verarbeiten.
Wenn du längere Zeilen hast, solltest du einen eigenen Puffer definieren und mit SetTextBuf() setzen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von corpsman »

@socke

da ich GCode files vesende müssten mir die default 128 reichen.

Wie ich gerade feststelle lese ich dei Datei aber doch im 2 pass.

Reicht es wenn ich nach dem Ersten Pass ein "reset(file)" mache oder muss ich die Datei schließen und neu öffnen ?
--
Just try it

Benutzeravatar
kralle
Lazarusforum e. V.
Beiträge: 985
Registriert: Mi 17. Mär 2010, 14:50
OS, Lazarus, FPC: Linux Mint 20 , FPC 3.3.1 , Lazarus 2.1.0 -Win10 & XE7Pro
CPU-Target: 64Bit
Wohnort: Bremerhaven
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von kralle »

Moin,

corpsman hat geschrieben:Ich sende den Text via Uart an einen 3D-Drucker ;)

Wenn jede Zeile ein Kommando für den Drucker enthält, was spricht dann gegen Zeilenweises Einlesen und an den Drucker senden?
Oder wäre das zu langsam, so das der Drucker Probleme damit hätte?

Gruß Heiko
OS: Manjaro Linux, Debian und Windows 10
FPC-Version: 3.2.2 , Lazarus 3.0
+ Delphi XE7SP1

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von corpsman »

Das mache ich im Prinzip auch so

Code: Alles auswählen

 
// Open File
// 1. Pas = Count all Commands
// reset File
// 2. Pas = Sende all Command + Aktualisiere den Fortschritsbalken
 


Problem, Anzahl Commands <> sl.count, deswegen muss ich im 1. Pass erst die Anzahl der tatsächlichen Befehle Berechnen.

[Edit]

Jetzt hab ichs geblickt, du meinst die File/IO auf der SD-Karte des Pi's.

Die war bisher ja kein Problem, da ich ja alles im Ram hatte, bin mal gespannt ob es jetzt zu einem Problem wird.
--
Just try it

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von Socke »

corpsman hat geschrieben:Reicht es wenn ich nach dem Ersten Pass ein "reset(file)" mache oder muss ich die Datei schließen und neu öffnen ?

Ich denke, reset(file) reicht aus. Ich musste gerade feststellen, dass es hier keine Möglichkeit gibt eine bestimmte Position anzuspringen.

corpsman hat geschrieben:Die war bisher ja kein Problem, da ich ja alles im Ram hatte, bin mal gespannt ob es jetzt zu einem Problem wird.

Wenn du den Aufwand treiben willst, erstellst du einen Lese-Thread mit Read Ahead und einen Sende-Thread :D
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Benutzeravatar
corpsman
Lazarusforum e. V.
Beiträge: 1496
Registriert: Sa 28. Feb 2009, 08:54
OS, Lazarus, FPC: Linux Mint Mate, Lazarus GIT Head, FPC 3.0
CPU-Target: 64Bit
Wohnort: Stuttgart
Kontaktdaten:

Re: TFiletream, zum Öffnen sehr sehr Großer Dateien..

Beitrag von corpsman »

*g*

ich habe schon einen eigenen Thread der das Senden macht.

Mein Raspi läuft hier im Consolenmodus und hat als Display ein C-Berry Display, eine Network Connection gibt es nicht -> Eigentlich müsste dem tierisch langweilig sein.

Da außer über die GPIO's er nirgens "beschäftigt" werden kann und die GPIO's fragt der Hauptthread ab.

Habe nun mal gemäß hier: https://www.raspberrypi.org/forums/view ... p?t=114228

die Textbuf Größe auf 4KB gesetzt, es wird sich in der Praxis zeigen ob ich den Extra Thread für den LookAhead brauche oder nicht..

Aktuelle Version:

Code: Alles auswählen

 
Unit uunbufstringlist;
 
{$MODE objfpc}{$H+}
 
Interface
 
Uses
  Classes, SysUtils;
 
Type
 
  { TUnbufferedStringList }
 
  TUnbufferedStringList = Class
  private
    IOBuffer: Array[0..(1024 * 4) - 1] Of byte; //  Der Raspi scheint eine 4KB Paging buffer size zu haben ..
    fFilename: String;
    ffile: TextFile;
    Procedure CloseFile;
  public
    Constructor Create;
    Destructor Destroy; override;
    (*
     * Datei wird beim erneuten Laden oder .free Automatisch geschlossen
     *)

    Function LoadFromFile(Const Filename: String): Boolean;
 
    (*
     * Zugriff auf die Daten..
     *)

    Procedure Reset();
    Function EOF: Boolean;
    Function Readln(): String;
  End;
 
Implementation
 
{ TUnbufferedStringList }
 
Constructor TUnbufferedStringList.Create;
Begin
  fFilename := '';
End;
 
Destructor TUnbufferedStringList.Destroy;
Begin
  CloseFile;
  Inherited Destroy;
End;
 
Procedure TUnbufferedStringList.CloseFile;
Begin
  If fFilename <> '' Then objpas.CloseFile(ffile);
End;
 
Function TUnbufferedStringList.LoadFromFile(Const Filename: String): Boolean;
Begin
  result := false;
  CloseFile;
  fFilename := '';
  If FileExists(Filename) Then Begin
    fFilename := Filename;
    assignfile(ffile, Filename);
    system.reset(ffile);
    system.SetTextBuf(ffile, IOBuffer);
    result := true;
  End;
End;
 
Procedure TUnbufferedStringList.Reset();
Begin
  system.Reset(ffile);
End;
 
Function TUnbufferedStringList.EOF: Boolean;
Begin
  result := true;
  If fFilename <> '' Then Begin
    result := system.EOF(ffile);
  End;
End;
 
Function TUnbufferedStringList.Readln(): String;
Begin
  result := '';
  If fFilename <> '' Then Begin
    system.Readln(ffile, result);
  End;
End;
 
End.
 
 
--
Just try it

Antworten