Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Große Textdateien spitten (https://www.delphipraxis.net/194214-grosse-textdateien-spitten.html)

muhael 29. Okt 2017 17:20

Delphi-Version: 7

Große Textdateien spitten
 
Hallo DP Team,

ich habe eine Funktion die große Textdateien in 4 Dateien splittet.
Nur diese Funktion ist "sehr" langsam, Sie benötigt für eine 1,5 GB große Textdatei 102 Sekunden.
Da die Dateien die mein Programm bearbeiten soll ungefähr in der Größenordnung von 3-4 GB liegen, braucht die Funktion dafür ca 4,5 Minuten und dies ist zu lang.

Gibt es eine Möglichkeit oder eine Funktion die das wesentlich schneller machen kann?
Wichtig zu wissen ist, dass die Datei nur Zeilenweise gesplittet werden kann.


Hier der Funktionscode;

Delphi-Quellcode:
  For i := 0 to 3 do
  begin
    AssignFile(F[i],ExtractFilePath(ParamStr(0)) + 'Test_' + IntToStr(i) + '.txt');
    ReWrite(F[i]);
  end;

  i := 0;
  AssignFile(ff,Epfad.Text);
  Reset(ff);
  while not eof(ff) do
  begin
    inc(i);
    ReadLn(ff, TestText);
    Case i of
      1: WriteLn(F[0],TestText);
      2: WriteLn(F[1],TestText);
      3: WriteLn(F[2],TestText);
      4: WriteLn(F[3],TestText);
    end;
    If i = 4 Then
      i := 0;
  end;
  CloseFile(F[0]);
  CloseFile(F[1]);
  CloseFile(F[2]);
  CloseFile(F[3]);
VG

Vielen Dank und Grüße

himitsu 29. Okt 2017 17:29

AW: Große Textdateien spitten
 
Wie lang sind die Zeilen?

Die einfachste Variante würde eine Vergrößerung des Caches bedeuten.
Die Uralten Funktionen AssignFile+TEXT nutzen einen Cache von 128 Byte je Datei und das ist natürlich extrems unökonomisch. (vorallem da alleine schon die Sektoren/Cluster des Datenträgers wesentlich größer sind)

Siehe OH von Reset/Rewrite.

Uwe Raabe 29. Okt 2017 17:31

AW: Große Textdateien spitten
 
Sehe ich das richtig: jede Zeile kommt in eine andere Datei und nach 4 Zeilen fängt es wieder von vorne an? In dem Fall kommst du um ein zeilenweises Lesen, wie es aktuell implementiert ist, wohl nicht herum.

Man könnte allenfalls noch was mit Textbuffern machen: Delphi-Referenz durchsuchenSetTextBuf

muhael 29. Okt 2017 17:35

AW: Große Textdateien spitten
 
Zitat:

Zitat von himitsu (Beitrag 1384479)
Wie lang sind die Zeilen?

Unterschiedlich groß. Die längste Zeile in meiner Testdatei ist 409 Zeichen lang.
Es kann aber sein, dass die Zeilen länger werden, da es sich um eine Fehlerdatei einer Datenintegritätsprüfung einer Datenbank handelt.

Hier mal eine Beispielzeile:
Warnung: 'Fibu Buch.-Blattname' existiert nicht. Buch.-Blattvorlagenname: ALLGEMEIN, Name: LOHN 02/06 Das Feld Buch.-Blattname in der Tabelle Fibu Buch.-Blattzeile hat eine Tabellenrelation auf das Feld Name in der Tabelle Fibu Buch.-Blattname. 'Fibu Buch.-Blattzeile' ist durch die folgenden Felder und Werte bestimmt: Buch.-Blattvorlagenname='ALLGEMEIN',Buch.-Blattname='LOHN 02/06',Zeilennr.='337000'




Zitat:

Zitat von Uwe Raabe (Beitrag 1384480)
Sehe ich das richtig: jede Zeile kommt in eine andere Datei und nach 4 Zeilen fängt es wieder von vorne an? In dem Fall kommst du um ein zeilenweises Lesen, wie es aktuell implementiert ist, wohl nicht herum.

Ungefähr ja, so war meine erste Idee.

Mein Programm analysiert diese Zeilen und diesen Prozess will ich beschleunigen indem ich die Datei in 4 gleich große Dateien splitte und in 4 Threads gleichzeitig bearbeite.

Rollo62 29. Okt 2017 17:40

AW: Große Textdateien spitten
 
Vielleucht könnte TStringReader schneller sein (glaub ich aber nicht), gibt es das in D7 ?
https://stackoverflow.com/questions/...iles-in-delphi

Ich würde eher versuchen Chunks in M;emory einzulesen, und die dann im Memory zu analysieren.

Rollo

himitsu 29. Okt 2017 19:19

AW: Große Textdateien spitten
 
Um es zu Beschleunigen willst du es also 5 Dateien lesen und 4 Dateien auf die "langsame" Platte schreiben?

Warum nicht die Datei einmal einlesen und dabei die gelesenen Zeilen direkt an die Threads weiterreichen?

muhael 29. Okt 2017 19:23

AW: Große Textdateien spitten
 
Zitat:

Zitat von himitsu (Beitrag 1384485)
Um es zu Beschleunigen willst du es also 5 Mal lesen und 4 Mal auf die "langsame" Platte schreiben?

Warum nicht die Datei einmal einlesen und dabei die gelesenen Zeilen direkt an die Threads weiterreichen?


Ich speichere ja die Werte zurück und Überprüfe daraufhin auf doppelte Werte. Da müssen ja dann die threads drauf warten bis die Überprüfung von einem thread fertig ist oder? 🤔

Glados 29. Okt 2017 19:28

AW: Große Textdateien spitten
 
Wie wäre es denn mit Memory Mapped Files von Herrn Jaenike?
Ließt unheimlich schnell ein und die Zeilen stehen auch schnell zur Verfügung.

http://www.delphipraxis.net/151898-s...ei-reader.html

nahpets 29. Okt 2017 19:39

AW: Große Textdateien spitten
 
Hab' mal ein bisserl rumgespielt, rausgekommen ist dashier:
Delphi-Quellcode:
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

const
//  BUFFER_SIZE = 16384; // 1:19
//  BUFFER_SIZE = 65536;  // 1:07
//  BUFFER_SIZE = 65536 * 2; // 1:03
  BUFFER_SIZE = 65536 * 4; // 0:53
//  BUFFER_SIZE = 65536 * 8; // 2:07
  arLow = 0;
  arHigh = 3;
var
  i       : Integer;
  f       : array[arLow..arHigh] of TextFile;
  ff      : TextFile;
  inf_buf : array[1..BUFFER_SIZE] of Byte;
  utf_buf : array[1..BUFFER_SIZE] of Byte;
  sIn     : String;
  sOut    : Array[arLow..arHigh] of String;
  sZeile  : String;
  dtStart : TDateTime;
  dtEnde  : TDateTime;
begin
  dtStart := Now;
  WriteLn('Start : ' + DateTimeToStr(dtStart));
  sIn    := ParamStr(1);

  For i := Low(f) to High(f) do begin
    sOut[i] := ExtractFilePath(ParamStr(0)) + 'Test_' + IntToStr(i) + '.txt';
    AssignFile(F[i],sOut[i]);
    SetTextBuf(F[i],utf_buf, BUFFER_SIZE);
    ReWrite(F[i]);
  end;

  i := 0;
  AssignFile(ff,sIn);
  SetTextBuf(ff,inf_buf,BUFFER_SIZE);
  Reset(ff);
  while not eof(ff) do begin
    ReadLn(ff, sZeile);
    WriteLn(F[i],sZeile);
    inc(i);
    If i > arHigh Then i := arLow;
  end;
  dtEnde := Now;
  WriteLn('Ende : ' + DateTimeToStr(dtEnde));
  WriteLn('Zeit : ' + TimeToStr(dtEnde - dtStart));
  WriteLn('Buffer: ' + IntToStr(Buffer_Size));
  WriteLn('Dateigroessen:');
  WriteLn('Eingabe: ',FileSize(ff) * Buffer_Size,' (',sIn,')');
  CloseFile(ff);
  for i := Low(f) to High(f) do begin
    WriteLn('Ausgabe: ',FileSize(f[i]) * Buffer_Size,' (',sOut[i],')');
    CloseFile(F[i]);
  end;
end.
Hinter der Konstante Buffer_Size steht jeweils, wielange mein Rechner für die Aufteilung einer Datei von 1.250.375.501 Byte benötigt hat.

Eventuell kannst Du damit ja was anfangen.

muhael 29. Okt 2017 20:03

AW: Große Textdateien spitten
 
Hallo Nahpets,

Ich konnte damit die benötigte Zeit von 102 Sekunden auf 25 Sekunden verringern.

Vielen Dank

Start : 29.10.2017 20:59:44
Ende : 29.10.2017 21:00:10
Zeit : 00:00:25
Buffer: 262144
Dateigroessen:
Eingabe: 1535639552 (TestDaten\dbtest_PINAG.txt)
Ausgabe: 383778816 (Test_0.txt)
Ausgabe: 383778816 (Test_1.txt)
Ausgabe: 383778816 (Test_2.txt)
Ausgabe: 383778816 (Test_3.txt)

Glados 29. Okt 2017 20:25

AW: Große Textdateien spitten
 
Versuch doch trotzdem mal http://www.delphipraxis.net/151898-s...ei-reader.html .

Das ist sicher noch schneller.

Luckie 29. Okt 2017 20:26

AW: Große Textdateien spitten
 
Und wie wäre es mit der TFileStream klasse?

muhael 29. Okt 2017 20:32

AW: Große Textdateien spitten
 
Zitat:

Zitat von Luckie (Beitrag 1384492)
Und wie wäre es mit der TFileStream klasse?

Läd der Filestream dann nicht die Datei in den Ram?

nahpets 29. Okt 2017 20:33

AW: Große Textdateien spitten
 
TFileStream hab' ich zuerst ausprobiert, war auf meinem Rechner deutlich langsamer (vorausgesetzt, dass ich es vernünftig gemacht haben sollte).

Man muss ja dann im Stream immer nach dem Zeilenumbruch suche und, wenn gefunden, die Ausgabedatei wechseln.

Sowas in der Art?
Delphi-Quellcode:
var
  output : Array[0..3] of TFileStream;
  input : TFileStream;
  i     : Integer;
  ch    : Char;
begin
  input := TFileStream.Create(ParamStr(1),fmOpenRead);
  for i := 0 to 3 do begin
    output[i] := TFileStream.Create(ExtractFilePath(ParamStr(0)) + 'Test_' + IntToStr(i) + '.txt',fmCreate);
  end;
  i := 0;
  input.Position := 0;
  input.Read(ch,1);
  repeat
    output[i].Write(ch,1);
    case ch of
      #10 : begin
              i := i + 1;
              if i > 3 then i := 0;
            end;
    end;
  until input.Read(ch, 1) = 0;
  for i := 0 to 3 do output[i].Free;
  input.Free;
end.

Luckie 29. Okt 2017 20:35

AW: Große Textdateien spitten
 
Wieso suchen? Ans Ende springen müsste doch reichen.

Fritzew 29. Okt 2017 20:38

AW: Große Textdateien spitten
 
Jetzt gebe ich auch noch meinen Senf dazu...
pragmatischer Ansatz
schon mal versucht die Datei im shared readmode für die einzelnen Threads zu öffnen und jedem thread mitzugeben welche
wievielte Zeile er zu lesen hat. Also so was wie
mytread := Tworkethread.create(onlyLine...)

wo dann onlyLine immer der Step ist den er liest also nur 1, 2,3,4... was auch immer Zeile?
Dann kannst Du dir die ganze splitterei sparen, die Information ist ja da..........

Luckie 29. Okt 2017 20:46

AW: Große Textdateien spitten
 
Das Problem dürfte dann aber sein, dass du zwar vier Threads hast, aber die Platte nur einen Schreibkopf. ;) Müsste man aber wohl ausprobieren.

Luckie 29. Okt 2017 20:49

AW: Große Textdateien spitten
 
Nachtrag: SSDs könne auch nur an einer Stelle gleichzeitig schreiben. Nur dürfte der Wechsel zu nächsten Speicherzelle schneller sein. Aber das ist ja der allgemeine Fall bei SSDs.

Fritzew 29. Okt 2017 20:54

AW: Große Textdateien spitten
 
Zitat:

Zitat von Luckie (Beitrag 1384501)
Das Problem dürfte dann aber sein, dass du zwar vier Threads hast, aber die Platte nur einen Schreibkopf. ;) Müsste man aber wohl ausprobieren.

Tja er kann sich aber das erzeugen von 4 Dateien sparen,
Ich weiss es jetzt auch nicht genau, aber ich denke wenn Du nur auf einer Datei liest und darum geht es ja letztendlich dürfte der filecache von Windows schon helfen,
Es ist letztendlich nur eine Datei offen anstatt 4. Meiner Meinung nach , ungetestet müsste das aber die schnellste Lösung sein. Immerhin reden wir von 4 minuten nur für das erzeugen bevor die threads überhaupt anfangen können......

Luckie 29. Okt 2017 20:55

AW: Große Textdateien spitten
 
Nachtrag 2: Bei HDDs macht es sich ja immer gut, wenn die Buffergröße sich an die Sectorgröße des Dateisystems orientiert. Gibt es so was bei SSDs eigentlich noch?

Luckie 29. Okt 2017 20:58

AW: Große Textdateien spitten
 
Moment. Du schreibst doch in vier? Ich habe jetzt euren Code nicht studiert, aber du hast eine zum Lesen geöffnet und vier zum schreiben. Oder schließt er die zu schreibenden Dateien immer wieder.

muhael 29. Okt 2017 20:59

AW: Große Textdateien spitten
 
Das Habe ich uach schon überlegt, aber jede zeile gibt nach der Analyse auch einen wert wieder, dieser wird mit den schon bestehenden Werten verglichen und geschaut ob er schon vorhanden ist, wenn nicht wird er hinzugefügt. Problem an 4 threads die jeweils eine Zeile haben ist ja dann, dass die threads warten müssen um auf den Speicher von der Liste zugreifen zu können

Fritzew 29. Okt 2017 21:00

AW: Große Textdateien spitten
 
Zitat:

Zitat von Luckie (Beitrag 1384505)
Moment. Du schreibst doch in vier? Ich habe jetzt euren Code nicht studiert, aber du hast eine zum Lesen geöffnet und vier zum schreiben. Oder schließt er die zu schreibenden Dateien immer wieder.

Lies den thread, er will das splitten damit da nachher 4 threads darauf zugreifen können. Deswegen meine Idee mit dem share read, kein shreiben vorher notwendig

muhael 29. Okt 2017 21:01

AW: Große Textdateien spitten
 
Zitat:

Zitat von Luckie (Beitrag 1384505)
Moment. Du schreibst doch in vier? Ich habe jetzt euren Code nicht studiert, aber du hast eine zum Lesen geöffnet und vier zum schreiben. Oder schließt er die zu schreibenden Dateien immer wieder.

ja ich habe 1 zum lesen und 4 zum schreiben geöffnet und schließe sie am ende der Funktion erst

nahpets 29. Okt 2017 21:05

AW: Große Textdateien spitten
 
Zitat:

Zitat von Luckie (Beitrag 1384497)
Wieso suchen? Ans Ende springen müsste doch reichen.

An das Ende von was springen?

1. Zeile -> Datei 1
2. Zeile -> Datei 2
3. Zeile -> Datei 3
4. Zeile -> Datei 4

Und wieder von vorne, bis die Datei vollständig aufgeteilt ist.

Irgendwie muss ich also für jede Zeile wissen, wo sie anfängt und wo sie aufhört. Da die Zeilen unterschiedlich lang sind, kann man ja nicht eine bestimmte Anzahl von Zeichen "am Stück" lesen.

Die Idee von Fritzew ist aber auch nicht von der Hand zu weisen. Man spart viel Plattenplatz und die entsprechenden Schreiboperationen.
Wenn wer jede vierte Zeile verarbeiten muss, kann er ja die übrigen Zeilen überlesen.
Und wo die erste Zeile für den Beginn ist, kann man ja mit einem Offset für die am Anfang zu überlesenden Zeilen angeben.

Und ob jetzt vier Threads vier Dateien lesen oder vier Threads eine Datei, dürfte für den Schreibkopf auch eher egal sein. Bzw.: Das was bei einer Datei gelesen wird, liegt physikalisch vermutlich näher beeinander, als bei auf mehrere Dateien verteilten Inhalten.

Die Sectorgröße ist doch abhängig vom Dateisystem und nicht von der Hardware. Oder irre ich da?

Eine Datei wird in 'ner Schleife gelesen und in vier wird geschrieben. Geöffnet wird am Anfang und am Ende alles geschlossen und nicht zeilenweise auf und zu.

Aber es wäre doch einen Versuch Wert, die Datei nicht zu splitten, sondern jeweils nur die vierte Zeile auszuwerten. Man muss nur am Anfang angeben, ob keine, eine, zwei oder drei Zeilen zu überlesen sind.

Fritzew 29. Okt 2017 21:06

AW: Große Textdateien spitten
 
Zitat:

Zitat von muhael (Beitrag 1384506)
Das Habe ich uach schon überlegt, aber jede zeile gibt nach der Analyse auch einen wert wieder, dieser wird mit den schon bestehenden Werten verglichen und geschaut ob er schon vorhanden ist, wenn nicht wird er hinzugefügt. Problem an 4 threads die jeweils eine Zeile haben ist ja dann, dass die threads warten müssen um auf den Speicher von der Liste zugreifen zu können

Das verstehe ich jetzt nicht,
Ob der Thread nun jede x zeile aus einem File oder jede Zeile aus „seinem“ file liest ist doch egal...
Ich meine ja gib dem Thread mit welche zeile er zu verarbeiten hat also jeder thread verarbeitet nur jede xte zeile. Wobei x für jeden anderst ist
Thread 1 ab erster zeile dann 3überspringen....
Thread 2 ab der 2 zeilw dann 3 überspringen..
Etc....

Glados 29. Okt 2017 21:09

AW: Große Textdateien spitten
 
Zitat:

Nachtrag 2: Bei HDDs macht es sich ja immer gut, wenn die Buffergröße sich an die Sectorgröße des Dateisystems orientiert. Gibt es so was bei SSDs eigentlich noch?
Dafür müsste man aber auch erstmal an diese Information gelangen.

Luckie 29. Okt 2017 21:11

AW: Große Textdateien spitten
 
Ah. Ja. Bei der zu lesenden Datei. Ich nehme alles zurück und behaupte das Gegenteil ;)

muhael 29. Okt 2017 21:17

AW: Große Textdateien spitten
 
Zitat:

Zitat von Fritzew (Beitrag 1384513)
Das verstehe ich jetzt nicht,
Ob der Thread nun jede x zeile aus einem File oder jede Zeile aus „seinem“ file liest ist doch egal...
Ich meine ja gib dem Thread mit welche zeile er zu verarbeiten hat also jeder thread verarbeitet nur jede xte zeile. Wobei x für jeden anderst ist
Thread 1 ab erster zeile dann 3überspringen....
Thread 2 ab der 2 zeilw dann 3 überspringen..
Etc....

Der Thread gibt auch Werte zurück.
falls er eine Zeile nicht analysieren konnte gibt er die Zeile zurück, diese wird in eine Datei geschrieben (Um nicht den Ram unnötig zu füllen, da ja die Dateien 3-4 GB groß sind)
Analysierte Zeilen werden zurückgegeben und mit der liste der bereits anderen Analysierten Zeilen verglichen und ggf hinzugefügt.

und wenn 4 Threads gleichzeitig 4 Zeilen bearbeiten und dann gleichzeitig die Liste abrufen wollen gibts ja probleme mit dem Zugriff darauf

Fritzew 29. Okt 2017 21:21

AW: Große Textdateien spitten
 
Zitat:

Zitat von muhael (Beitrag 1384517)
Zitat:

Zitat von Fritzew (Beitrag 1384513)
Das verstehe ich jetzt nicht,
Ob der Thread nun jede x zeile aus einem File oder jede Zeile aus „seinem“ file liest ist doch egal...
Ich meine ja gib dem Thread mit welche zeile er zu verarbeiten hat also jeder thread verarbeitet nur jede xte zeile. Wobei x für jeden anderst ist
Thread 1 ab erster zeile dann 3überspringen....
Thread 2 ab der 2 zeilw dann 3 überspringen..
Etc....

Der Thread gibt auch Werte zurück.
falls er eine Zeile nicht analysieren konnte gibt er die Zeile zurück, diese wird in eine Datei geschrieben (Um nicht den Ram unnötig zu füllen, da ja die Dateien 3-4 GB groß sind)
Analysierte Zeilen werden zurückgegeben und mit der liste der bereits anderen Analysierten Zeilen verglichen und ggf hinzugefügt.

und wenn 4 Threads gleichzeitig 4 Zeilen bearbeiten und dann gleichzeitig die Liste abrufen wollen gibts ja probleme mit dem Zugriff darauf

Das ist aber jetzt ein ganz anderes problem, wo helfen Dir da verschiedene Dateien dür den Input?
Im Gegenteil es wird sogar einfacher zu analysieren da es nur einen input gibt

nahpets 29. Okt 2017 21:23

AW: Große Textdateien spitten
 
Zitat:

Zitat von muhael (Beitrag 1384517)
und wenn 4 Threads gleichzeitig 4 Zeilen bearbeiten und dann gleichzeitig die Liste abrufen wollen gibts ja probleme mit dem Zugriff darauf

Ja, aber das ist unabhängig davon, ob die Threads nun jeweils eine eigene Datei lesen oder in einer Datei jeweils die vierte Zeile.

himitsu 30. Okt 2017 10:15

AW: Große Textdateien spitten
 
Darum liest nur Einer die Datei und gibt die Zeilen an die Threads weiter. (1)
Oder man macht z.B. eine CriticalSection um den Lesebefehl, bei jedem Thread. (2)



Außerdem ist es blöde ungünstig Einzelzeilen weiterzureichen/zu lesen.

Wenn sich jeder Thread (2) mehrere Zeilen holt und sie dann verarbeitet, bis er sich den nächsten Pack holt,
oder wenn man mehrere Zeilen z.B. in eine Liste/Array einfügt und diese Listen dann an einen der Threads übergibt (1 ... also Liste abarbeiten und dann Freigeben)
oder wenn man jeder Thread schon eine Liste/Array hat und der Lesethread diese Liste dann regelmäßig mit mehreren Zeilen befüllt, wenn die Liste leer oder zu leer ist (2) ...
> So kann man die Sperrenden Zugriffe verringern.

In neueren Delphis gibt es nicht nur Threads, sondern auch Tasks, da kann man z.B. jeweils 100 Zeilen Lesen und dafür erstellt man eine Task und diese Tasks lässt man dann z.B. in 4 Threads abarbeiten.

Glados 30. Okt 2017 11:11

AW: Große Textdateien spitten
 
Zitat:

In neueren Delphis gibt es nicht nur Threads, sondern auch Tasks, da kann man z.B. jeweils 100 Zeilen Lesen und dafür erstellt man eine Task und diese Tasks lässt man dann z.B. in 4 Threads abarbeiten.
Wie wäre es denn mit der For-Schleife aus der PPL?


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:12 Uhr.

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