Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Viele Dateien performant einlesen (https://www.delphipraxis.net/180173-viele-dateien-performant-einlesen.html)

Der schöne Günther 28. Apr 2014 11:12

Viele Dateien performant einlesen
 
Ich habe eine Vielzahl (ein paar Tausend) CSV-Dateien. Die möchte ich allesamt auswerten.

Der naive Ansatz
  1. Datei einlesen mit
    Delphi-Quellcode:
    TStrings::LoadFromFile(..)
  2. Datei zerstückeln mit
    Delphi-Quellcode:
    myStrings.Split(..)
  3. Eventuell weitere Verarbeitung

funktioniert auch bestens. Es dauert aber zu lange. Bei ca. 5000 Dateien zu 24 KB braucht das Delphi-Programm auf meinem System (mit SSD) ca. 45 Sekunden.

Der Flaschenhals ist eindeutig (und überraschenderweise)
Delphi-Quellcode:
TStringList::LoadFromFile
. Lade ich genau die gleiche Datei ein zweites mal ein, ist das in "Null Millisekunden" getan.

Es wird also technisch:
  • Windows
  • NTFS-Dateisystem. Clustergröße wahrscheinlich fest 512 Bytes.
  • Eine Menge von Dateien im gleichen Verzeichnis möglichst schnell in den Speicher befördern

Was muss ich tun? Gibt es etwas, um alle Dateien einmal am Stück zu holen? Windows' Memory Mapped Files habe ich bislang immer nur für das Gegenteil- Viele, andauernde Operationen auf großen Dateien gehört...

Uwe Raabe 28. Apr 2014 11:15

AW: Viele Dateien performant einlesen
 
Beim zweiten Mal ist die Datei noch im Cache, dann geht das schneller.

Evtl. der Virenscanner?

Der schöne Günther 28. Apr 2014 11:19

AW: Viele Dateien performant einlesen
 
An Virenscanner habe ich jetzt nicht gedacht. Aber ja, das sollte ich auch mal testen.

Aber dass es am Windows-Datei-Cache liegt ist klar. Ich weiß von Anfang an, welche Dateien ich mir jetzt anschauen werde- Deshalb muss ich die doch sicher alle auf einmal einlesen können?

Uwe Raabe 28. Apr 2014 11:47

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1257236)
An Virenscanner habe ich jetzt nicht gedacht. Aber ja, das sollte ich auch mal testen.

Aber dass es am Windows-Datei-Cache liegt ist klar. Ich weiß von Anfang an, welche Dateien ich mir jetzt anschauen werde- Deshalb muss ich die doch sicher alle auf einmal einlesen können?

Das wird an der Zeit, die Windows zum Lesen braucht, nicht viel ändern.

Der schöne Günther 28. Apr 2014 12:03

AW: Viele Dateien performant einlesen
 
Ich weiß nicht. Deshalb frage ich ja.

Meine Hoffnung ist, dass es deutlich besser wird zu sagen
Zitat:

Lies alle Dateien im Verzeichnis X
als 5000x zu sagen
Zitat:

Lies Datei X

himitsu 28. Apr 2014 12:05

AW: Viele Dateien performant einlesen
 
Nein, kannst du nicht.

Du kannst maximal das machen, was z.B. MSOffice, Adobe und Co. machen. Die haben einen Service im Hintergrund laufen, welcher ihre (vermutlich) benötigten Dateien bereits beim Windowsstart und regelmäßig immer wieder läd, damit sie in der Windows-File-Cache landen und es dann schneller geht, wenn du irgendwann eventuell mal dein Office startest.



Du könntest die Dateiliste schneller erstellen, denn auch das auslesen (vorallem großer Verzeichnisse) dauert "ewig".
Aber dafür müsstest du dich z.B. direkt an die MFT wenden. (aber auch so kann man ein PreFetch erzwingen, indem das Verzeichnis schonmal vorher in den WFC geladen wird)

OK, wenn du Admin Backup-Rechte besitzt, dann kannst du die Dateien direkt aus dem Dateisystem ziehen und wenn du diese vorher schön hintereinander gelegt hast (defragmentiert und neu positioniert), dann könntest du sie nun in einem Ruck schnell einlesen. :stupid:

Union 28. Apr 2014 12:14

AW: Viele Dateien performant einlesen
 
Du könntest aber mehrere Threads zur Verarbeitung einsetzen, vielleicht wird es dann schneller.

Dejan Vu 28. Apr 2014 12:17

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Union (Beitrag 1257244)
Du könntest aber mehrere Threads zur Verarbeitung einsetzen, vielleicht wird es dann schneller.

Auch bei einer SSD? Bei ner HDD ist der Lesekopf doch der Flaschenhals,...

himitsu 28. Apr 2014 12:21

AW: Viele Dateien performant einlesen
 
Bei einer SSD kommt es auf die Controller an, wie viele Leseoperationen dort parallel behandelt werden können. Und auch auf die anderen Caches, wie und ob sie beim MultiThreading die Daten verwalten/blocken.

Bei Festplatten macht ein paralleles Lesen alles viel Schlimmer, da der Lesekopf dort womöglich/bestimmt mehr hin und her rennen muß, als wenn man das sequentiell lesen würde ... Besser wird nichts, höchstens langsamer. (außer die Daten liegen bereits im Cache)



Das Einzige was man noch machen kann, ist die Cache zu umgehen. (MSDN-Library durchsuchenFILE_FLAG_NO_BUFFERING)
Beim "ersten" Laden ist es dann einen Hauch schneller, wenn andere Daten nicht erst aus dem Cache rausgeworfen werden müssen.
Aber beim zweiten Durchgang ist es dann langsamer, da die Dateien ja nicht im Cache gelandet sind.
Allerdings ist es insgesamt dennoch schneller, wenn man mehr Daten lesen will, als freier RAM zur Verfügung steht.

Deine Dateien sind aber auch so klein, daß vermutlich MSDN-Library durchsuchenFILE_FLAG_SEQUENTIAL_SCAN nichts bringt, da es impliziet schon gemacht wurde, vorallem wenn die Sektorgröße Clustergröße groß genug ist.


MMF ist eher für RandomAccess geeignet, oder wenn man direkt in dem speicher mit Pointern rumrennen will, ohne einen eigenen Buffer verwalten zu müssen.
Und bei so kleinen Dateien bringt das keinen großen Vorteil. Wobei auch und vorallem hier die Datei erst (nahezu unkontroliert) vom windows erst in den WFC geladen werden muß, dessen Speicher dann windows in deinen virtuellen RAM mappt/verlinkt.

user0815 28. Apr 2014 12:22

AW: Viele Dateien performant einlesen
 
Hatte hier im Forum mal was von einem "BULK INSERT" gelesen, habe allerdings keine Ahnung (da noch nie gemacht) ob dieser auch für viele Dateien geeignet ist.

mkinzler 28. Apr 2014 12:44

AW: Viele Dateien performant einlesen
 
Bulkinsert ist eine Verfahren für den schneller Import in einen (MS)SQL-Server.

himitsu 28. Apr 2014 12:50

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von mkinzler (Beitrag 1257248)
Bulkinsert ist eine Verfahren für den schneller Import in einen (MS)SQL-Server.

Aber auch dieser der schnelle Insert wird die Dateien einzeln einlesen müssen.

Das Einzige was ihn noch einen Hauch schneller machen könnte, ist dann die Behandlung der eingelesenen Daten.


Seit Delphi 2009 arbeiten StringStream, StringList und co. noch mit einem zusätzlichem Zwischenschritt, wo das Encoding auf Unicode umgewandelt wird.

Uwe Raabe 28. Apr 2014 13:16

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von himitsu (Beitrag 1257249)
Das Einzige was ihn noch einen Hauch schneller machen könnte, ist dann die Behandlung der eingelesenen Daten.


Seit Delphi 2009 arbeiten StringStream, StringList und co. noch mit einem zusätzlichem Zwischenschritt, wo das Encoding auf Unicode umgewandelt wird.

Aber Günther sagt doch, daß es beim zweiten Mal deutlich schneller geht. Somit kann man mit einer Optimierung des Post-Read-Vorgangs auch nur einen Teil dieser Zeit vom zweiten Lesevorgang einsparen. Das halte ich für ziemlich ineffizient.

Der schöne Günther 28. Apr 2014 13:31

AW: Viele Dateien performant einlesen
 
Ich muss zugeben, keine sonderlich ausführlichen Tests gefahren zu haben. Zwei mal
Delphi-Quellcode:
myStringList.ReadFromFile(..)
auf die exakt gleiche Datei hintereinander. Das zweite mal geht praktisch sofort. Das meinte ich.

In den Innereien von
Delphi-Quellcode:
TFileStream
habe ich jetzt nicht gewühlt...

Blup 28. Apr 2014 13:35

AW: Viele Dateien performant einlesen
 
Ich schlage vor du änderst das Konzept.
Fass die z.B. 5000 Dateien zu einer zusammen, die im ersten Schritt importiert wird.
Sollte sich eine der Dateien nachträglich ändern, wird die geänderte Version zusätzlich in das Verzeichnis gespeichert.
Im zweiten Schritt werden diese Änderungen importiert.
Nützlich ein optionaler dritter Schritt, der die Datei mit allen Änderungen wieder erstellt und die dann überflüssigen Einzeldateien löscht.
Alternativ zum Löschen kann man auch mit den Dateiattributen(Archiv) oder Änderungszeitpunkt arbeiten.

Dejan Vu 28. Apr 2014 18:04

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Blup (Beitrag 1257259)
Fass die z.B. 5000 Dateien zu einer zusammen, die im ersten Schritt importiert wird.

Dann muss man die Dateien aber auch alle einlesen. Und wenn man sie schon mal eingelesen hat, kann man sie auch gleich verarbeiten
Sollte sich eine der Dateien nachträglich ändern, wird die geänderte Version zusätzlich in das Verzeichnis gespeichert.
Wenn sich die Dateien immer mal wieder ändern oder neue hinzukommen, würde ich ein ShellNotify auf das Verzeichnis setzen, dann hat man den zeitlichen Aufwand nur beim ersten Mal. Mit dem ShellNotify muss man aber das Konzept nicht mehr ändern.

jfheins 28. Apr 2014 19:44

AW: Viele Dateien performant einlesen
 
Schau mal, was passiert, wenn du direkt mit ReadFile() aus der WinAPI arbeitest.

Da du ja anscheinend weißt, dass deine Dateien <50KB sind, würde ich einfach mal 50KB Puffer reservieren und die Daten (damit möglichst "auf einmal") lesen. Bei einer SSD auch ruhig mal mit 2 Threads parallel testen ob es schneller ist.

himitsu 28. Apr 2014 19:53

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von jfheins (Beitrag 1257306)
Schau mal, was passiert, wenn du direkt mit ReadFile() aud der WinAPI arbeitest.

Oder TFileStream, welches auch blos das ReadFile aufruft. :angel:

Wobei auch TStringList alles auf einmal einliest. (stückchenweise ist denen von Emba und eigentlich fast Allen zu aufwändig :roll:)

Bentissimo 29. Apr 2014 09:24

AW: Viele Dateien performant einlesen
 
Ich hatte mal ein halbwegs vergleichbares Problem. Die Aufgabe war, Text-Dateien der Größe 2-4 GB nach Mustern zu durchsuchen. In einem Moment des Denkverzichts hatte ich versucht eine solche Datei mit Notepad zu öffnen. :oops:

Anschließend habe ich stattdessen Edit++ verwendet. Der konnte "ganz locker" damit umgehen. Mit der FastStr-Bibliothek hats dann auch in meinem Programm sehr gut geklappt. Wenn ich mich recht erinnere lagen die Zeiten für das Lesen & Durchsuchen unter 10 Sekunden.

Langer Rede, kurzer Sinn. Ich würde mal versuchen mit Edit++ oder sonst einem vergleichbaren Editor alle Dateien auf einen Rutsch zu öffnen und die Ladezeit (abzüglich einem eventuellen Overhead für den Aufbau der GUI) mit Deinen Zahlen zu vergleichen.

Sollte das Ergebnis besser ausfallen, machen diese Editoren womöglich irgendwas anders als ein "TStringList.LoadFromFile" und ich würde das bereits vorgeschlagene Zusammenfassen der kleinen Dateien zu einer großen ernsthaft in Erwägung ziehen. Auch wenn dann weitere oder andere Herausforderungen auf Dich zukommen.

Möglich, dass ich hier Äpfel mit Birnen vergleiche, aber vielleicht hilft es ja doch irgendwie. :roll:

Mavarik 29. Apr 2014 09:36

AW: Viele Dateien performant einlesen
 
ca. 120 MB? LOL ist doch heute nix mehr...

Blockread direkt ins Ram aller Dateien und dann verarbeiten?

Mavarik

Dejan Vu 29. Apr 2014 09:43

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Mavarik (Beitrag 1257368)
ca. 120 MB? LOL ist doch heute nix mehr...
Blockread direkt ins Ram aller Dateien und dann verarbeiten?

Es handelt sich um 5000 Dateien, die dann auch 5000 -lol- Mal per Blockread eingelesen werden müssen. Das geht dann auch nicht schneller als 'LoadFromFile'.

Imho kann man höchstens die Tatsache ausnutzen, das es sich um eine SSD handelt und die Dateien wirklich über mehrere Threads einlesen. Die Directory ist ja schnell gelesen und so ein Thread zum einlesen ist auch Einsfixdrei gebaut. Dann kann man doch einfach prüfen, ob es was bringt...

Blup 29. Apr 2014 09:50

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Dejan Vu (Beitrag 1257299)
Zitat:

Zitat von Blup (Beitrag 1257259)
Fass die z.B. 5000 Dateien zu einer zusammen, die im ersten Schritt importiert wird.

Dann muss man die Dateien aber auch alle einlesen. Und wenn man sie schon mal eingelesen hat, kann man sie auch gleich verarbeiten

Nicht unbedingt, das hängt vom Anwendungsfall ab. Bei einem einmaligen Import wäre die Zeit dafür sicher nicht das Thema.

Wir hatten den Fall, der Katalog eines Herstellers besteht aus mehr als 1000 kleinen Dateien, die die täglichen Änderungen der Stammdaten enthalten und ständig erweitert werden. Unser Testsystem importiert alle Änderungen und wir erstellen ca. einmal im Jahr oder bei Bedarf eine große Datei. Wenn unsere Kunden diesen Katalog benötigen, wird bei der Installation sehr viel Zeit gespart. Neben der großen Datei müssen nur die nachfolgenden Änderungen importiert werden.

Sir Rufo 29. Apr 2014 09:51

AW: Viele Dateien performant einlesen
 
Bei diesem Ansatz kann man durch einen Thread etwas mehr herausholen.

Einer Klasse wird der Dateiname übergeben. Diese Klasse hat eine Eigenschaft, die diese Datei als TStringList (oder TStrings) zur Verfügung stellt. Allerdings wird beim Zugriff auf diese Eigenschaft erst der Inhalt der Datei geladen (wenn dies in der Zwischenzeit noch nicht erfolgt ist).

Wenn sich nun jede Instanz an einen Thread hängt, der das Laden der Dateien in den Instanzen bewirkt, dann kann die Verarbeitung und das Laden der Datei in unterschiedlichen Threads erfolgen und die Zeit reduziert sich im optimalen Fall auf die reine Ladezeit / Verarbeitungszeit der Dateien (je nachdem, was länger dauert).

Wenn das reine Laden 30 Sekunden dauert und die Verarbeitung 15 Sekunden, dann kann das gesamte Laufzeitverhalten bis auf 30 Sekunden reduziert werden.

Das geht natürlich nur, wenn die Verarbeitung keine Festplatten-Aktionen erfordert, denn dadurch wird ja die Ressource Festplatte wieder eingebunden.

UPDATE

Wenn die reine Verarbeitung nur einen Bruchteil der gesamten Abarbeitung beansprucht, wird dieses Konstrukt aber uninteressant und schafft nur eine weitere Komplikation :)

Dejan Vu 29. Apr 2014 10:51

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Sir Rufo (Beitrag 1257373)
Wenn die reine Verarbeitung nur einen Bruchteil der gesamten Abarbeitung beansprucht, wird dieses Konstrukt aber uninteressant und schafft nur eine weitere Komplikation :)

Genau, außer vielleicht bei einer SSD oder einer Platte mit mehreren Leseköpfen. Da könnte es etwas bringen.

p80286 29. Apr 2014 11:38

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Dejan Vu (Beitrag 1257379)
... oder einer Platte mit mehreren Leseköpfen. Da könnte es etwas bringen.

Möglich aber unwahrscheinlich, da dann auch die Verwaltungsinformation "in der Nähe" der Daten liegen müßte. (alle Daten auf einem Zylinder)

Gruß
K-H

Union 29. Apr 2014 11:53

AW: Viele Dateien performant einlesen
 
Letztendlich sind das alles Ratespiele. Warum nicht zielgerichtet vorgehen und einen Profiler einsetzen. Dann die Teile mit dem größten Einfluss auf die Performance optimieren, indem man z.b. Das Encoding selber setzt anstatt das die Stringliste selber machen zu lassen usw.

Sir Rufo 29. Apr 2014 15:09

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Union (Beitrag 1257382)
Letztendlich sind das alles Ratespiele. Warum nicht zielgerichtet vorgehen und einen Profiler einsetzen. Dann die Teile mit dem größten Einfluss auf die Performance optimieren, indem man z.b. Das Encoding selber setzt anstatt das die Stringliste selber machen zu lassen usw.

Hat er doch schon gemacht (siehe ersten Beitrag) aber nicht explizit aufgeschlüsselt, wieviel Zeit jeder Schritt beansprucht.

Mit der OmniThreadLibraray könnte man sich auch eine Pipeline bauen, wo Einlesen und Verarbeiten separate Schritte sind. Dann kann man darauf weiter aufbauen und jeden Schritt nochmals in sich optimieren.

Union 29. Apr 2014 16:28

AW: Viele Dateien performant einlesen
 
Ja, aber er hat LoadFromFile nicht profiled. Wenn man das echt instrumentieren will, muß man notfalls eine Kopie davon machen, an der man profiled und optimiert. Zum Schluß hat man dann automatisch eine "Ladeoptimierte" TStringList.

himitsu 29. Apr 2014 20:01

AW: Viele Dateien performant einlesen
 
Da es aber schnell genug läuft, wenn die Dateien schon im Cache liegen, dann ist das Encoding der TStringList vernachlässigbar.

Ansonsten hatte ich ja eigentlich schon alles erklärt.

Union 29. Apr 2014 20:31

AW: Viele Dateien performant einlesen
 
Fragt sich was das für ein Cache ist? Wenn ich folgendes Fragment vorschalte, in dem ich die Daten komplett einlese, wird das Verarbeiten danach wesentlich schneller:
Delphi-Quellcode:
  for i := Low(AFiles) to High(AFiles) do
  begin
    Watch.Start;
    fs := TFileStream.Create(AFiles[i], fmExclusive);
    SetLength(Buffer, fs.Size);
    fs.ReadBuffer(Buffer[0], fs.Size);
    Watch.Stop;
    fs.Free;
  end;
Diese Funktion braucht für 5.000 frisch erzeugte Dateien zwischen 500-1500 ms.

Wenn ich dann die Daten nochmals in Stringlisten einlese, werden 605-615 ms verbraucht.

Starte ich das Einlesen in die Stringlisten ohne den Vorlauf, braucht das beim ersten Mal 12000-13000 ms.

Vorlauf1500
Verarbeitung615
Summe2115
Direkte Verarbeitung13000

D.h. dieses Verfahren ist 6x schneller!

Komplettes Testprogramm:
Delphi-Quellcode:
unit main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Diagnostics;

type
  TForm1 = class(TForm)
    btnCreateData: TButton;
    btnRead: TButton;
    MemoProt: TMemo;
    btnOpen: TButton;
    procedure btnCreateDataClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure btnReadClick(Sender: TObject);
    procedure btnOpenClick(Sender: TObject);
  private
    { Private-Deklarationen }
    FDir : string;
    FWatch : TStopWatch;
    procedure Prot(const AMessage : string; AWatch : TStopWatch);
  public
    { Public-Deklarationen }
    property Watch : TStopWatch read FWatch;
  end;

var
  Form1: TForm1;

implementation

uses
  System.Types,
  System.IOUtils,
  System.StrUtils;

{$R *.dfm}

procedure TForm1.btnCreateDataClick(Sender: TObject);
var
  i : integer;
  Line : string;
begin
  Watch.Start;
  Line := Dupestring('ABCDEFGHI;', 2400);
  for i := 1 to 5000 do
    TFile.WriteAllText(TPath.Combine(FDir, Format('%4.4d.txt', [i])), Line);
  Watch.Stop;
  Prot('CreateFiles', Watch);
  Watch.Reset;
end;

procedure TForm1.btnOpenClick(Sender: TObject);
var
  AFiles : TStringDynArray;
  fs : TFileStream;
  i : integer;
  Buffer : TByteDynArray;
begin
  Watch.Start;
  AFiles := TDirectory.GetFiles(FDir);
  Watch.Stop;
  Prot('GetFiles', Watch);
  Watch.Reset;
  for i := Low(AFiles) to High(AFiles) do
  begin
    Watch.Start;
    fs := TFileStream.Create(AFiles[i], fmExclusive);
    SetLength(Buffer, fs.Size);
    fs.ReadBuffer(Buffer[0], fs.Size);
    Watch.Stop;
    fs.Free;
  end;
  Prot('Open', Watch);
  Watch.Reset;
end;

procedure TForm1.btnReadClick(Sender: TObject);
var
  AFiles : TStringDynArray;
  i : integer;
  Line : string;
  sl : TStringList;
  SplitWatch : TStopWatch;
begin
  Watch.Start;
  AFiles := TDirectory.GetFiles(FDir);
  Watch.Stop;
  Prot('GetFiles', Watch);
  Watch.Reset;

  sl := TStringList.Create;
  sl.Delimiter := ';';
  try
    SplitWatch := TStopWatch.Create;
    for i := Low(AFiles) to High(AFiles) do
    begin
      Watch.Start;
      Line := TFile.ReadAllText(AFiles[i]);
      Watch.Stop;
      SplitWatch.Start;
      sl.DelimitedText := Line;
      SplitWatch.Stop;
    end;
    Prot('ReadFiles', Watch);
    Prot('Split', SplitWatch);
  finally
    SplitWatch.Reset;
    Watch.Reset;
    sl.Free;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FDir := TPath.Combine(TPath.GetDirectoryName(Application.ExeName), 'Data');
  TDirectory.CreateDirectory(FDir);
  FWatch := TStopWatch.Create;
end;

procedure TForm1.Prot(const AMessage : string; AWatch : TStopWatch);
begin
  MemoProt.Lines.Add(AMessage + ' ' +AWatch.ElapsedTicks.ToString+' ticks, '+AWatch.ElapsedMilliseconds.ToString+' ms');
end;

mensch72 29. Apr 2014 20:45

AW: Viele Dateien performant einlesen
 
Liste der Anhänge anzeigen (Anzahl: 1)
..."Ich habe eine Vielzahl (ein paar Tausend) CSV-Dateien. Die möchte ich allesamt auswerten."...

Nun ja, ich hätte da ein paar Millionen(~3Mio) Dateien(a ~8KB) in ein paar tausend Verzeichnissen(~180000) und renne da ständig durch...

wo ist das Problem:
- über alle Dateien
* FileStream öffnen und StreamSize ermitteln
* bei den "Spielzeuggrößen" mit einem Schlag vom FileStream in RAM Buffer einlesen
* FileStream freigeben(schließen)
* keine Verarbeitung oder sonst was!
* nächste Datei
- Zeit messen

Man könnte auch hier zwar per WinApi Funktionen und direktem RAM Puffer ein paar MicroSekunden sparen, aber FileStream reicht hier vollkommen aus.
(Nebenbei: Bei "großen Dateien" einfach mal fach TFastfileStream googeln... da findet sich dann was schönes mit MemoryMappedFiles:))

Wenn das OK, dann mit etwas Hirnschmalz ein eigenes "GetLine" geschrieben(einfach per BYTE Pointer durch den Rambuffer nach "CR" suchen und wenn CR gefunden es durch "NULL" ersetzen und die nun "NullTerminierte" Line ab vorherigem Pointer(bzw. anfangs "@0") weiter auswerten.

Gleiches Spiel nun innerhalb der Line. CSV-Trennzeichen fortlaufend suchen und gefundene Positionen+1 in BytePtrArray speichern und Inhalt des Pointers auf "0" setzen... Bingo: nun haben wir ohne jegliches Umkopieren irgendwelcher Speicher/Strings ein PtrArray mit lauter Pointern vom Typ PChar(bzw. PAnsiChar), welche wir problemlos verarbeiten, vergleichen, kopieren oder was auch immer können...


(Sorry für meine Vorliebe für NullTerminierte Strings auf Pointer-Basis, ich denke immer noch in "C" und nutze Delphi/Pascal nur weil es sein muss;))

TStringList ist zwar schön, aber im Masseneinsatz ist deren LoadFromXXX als "ReadLN" doof. Wenn es schnell gehen soll, dann 3 Threads... 2x FileStreamLoad, 1x "RAM-CSV-Explode"... Results dann in weiterem Thread speichern/verarbeiten... damit sollte QuadCore gut ausgelastet werden.

Blup 30. Apr 2014 12:12

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von Union (Beitrag 1257448)
Fragt sich was das für ein Cache ist? Wenn ich folgendes Fragment vorschalte, in dem ich die Daten komplett einlese, wird das Verarbeiten danach wesentlich schneller:
Delphi-Quellcode:
  for i := Low(AFiles) to High(AFiles) do
  begin
    Watch.Start;
    fs := TFileStream.Create(AFiles[i], fmExclusive);
    SetLength(Buffer, fs.Size);
    fs.ReadBuffer(Buffer[0], fs.Size);
    Watch.Stop;
    fs.Free;
  end;
Diese Funktion braucht für 5.000 frisch erzeugte Dateien zwischen 500-1500 ms.

Wenn ich dann die Daten nochmals in Stringlisten einlese, werden 605-615 ms verbraucht.

Starte ich das Einlesen in die Stringlisten ohne den Vorlauf, braucht das beim ersten Mal 12000-13000 ms.

Vorlauf1500
Verarbeitung615
Summe2115
Direkte Verarbeitung13000

D.h. dieses Verfahren ist 6x schneller!

Ich musste ein par Sachen anders lösen um das Programm mit Delphi 2007 zum Laufen zu bringen.
Die von dir gemessenen Ergebnisse kann ich aber überhaupt nicht nachvollziehen:
Code:
CreateDataClick
         CreateFiles 0:00:14.527
ReadClick
            GetFiles 0:00:00.007
           ReadFiles 0:00:01.692
               Split 0:00:03.112

CreateDataClick
         CreateFiles 0:00:09.681
OpenClick
            GetFiles 0:00:00.006
                Open 0:00:02.614
ReadClick
            GetFiles 0:00:00.006
           ReadFiles 0:00:01.649
               Split 0:00:03.336

p80286 30. Apr 2014 12:34

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von mensch72 (Beitrag 1257450)
Wenn das OK, dann mit etwas Hirnschmalz ein eigenes "GetLine" geschrieben(einfach per BYTE Pointer durch den Rambuffer nach "CR" suchen und wenn CR gefunden es durch "NULL" ersetzen und die nun "NullTerminierte" Line ab vorherigem Pointer(bzw. anfangs "@0") weiter auswerten.

Und was machst Du mit "dies sind[CRLF]zwei zeilen"?
Nach Deiner Methode mußt Du jede erstellte Zeile überprüfen ob sie vollständig ist, da doch lieber gleich richtig.

Gruß
K-H

Der schöne Günther 30. Apr 2014 13:08

AW: Viele Dateien performant einlesen
 
Doch, also bei mir ist es ähnlich:

Code:
openClick
GetFiles 50269 ticks, 16 ms
Open 745099 ticks, 238 ms


readClick
GetFiles 90555 ticks, 29 ms
ReadFiles 3411398 ticks, 1094 ms
Split 15388554 ticks, 4935 ms

stringListRead 3179206 ticks, 1019 ms
Delphi-Quellcode:
stringListRead
ist jetzt einfach nur ein TStringList-LoadFromFile, also im Endeffekt praktisch das gleiche wie ReadFiles nur mit einem leicht anderen
Delphi-Quellcode:
FileMode
zum Öffnen der Dateien.

Jetzt das Wichtige: Wir übersehen bei diesen Benchmarks hier alle den Windows-Cache hinter den Kulissen! Ich war grade auch verdutzt über meine Zeiten: Wie kam ich denn auf 45 Sekunden?

Dann habe ich mit RAMMap einmal den Windows-Datei-Cache geleert und statt ca. einer Sekunde zum Lesen bin ich nun wieder bei locker 30 Sekunden. Nur für das Lesen der Dateien.

Um aussagekräftige Messungen zu machen sollte man wohl vor jedem Durchgang einmal komplett den Windows File Cache leeren. Das werde ich im Verlauf des Tages auch nochmal machen...



Auf jeden Fall vielen Dank für die rege Teilnahme an alle! :thumb:

Dejan Vu 30. Apr 2014 13:57

AW: Viele Dateien performant einlesen
 
Zitat:

Zitat von p80286 (Beitrag 1257504)
Zitat:

Zitat von mensch72 (Beitrag 1257450)
...(einfach per BYTE Pointer durch den Rambuffer nach "CR" suchen und wenn CR gefunden es durch "NULL" ersetzen und die nun "NullTerminierte" ...

...da doch lieber gleich richtig....

Für das schnelle Lesen von CSV-Dateien gibt es hier doch eine schöne Lösung. Die kommt mit allen möglichen Sonderspezialfällen klar und ist zudem noch sehr schnell. Nur die Datei *einlesen* muss sie auch und da führt kein Weg dran vorbei.

Union 30. Apr 2014 15:48

AW: Viele Dateien performant einlesen
 
Die Antwort ist 134217728 ;)

Delphi-Quellcode:
procedure TForm1.btnOpenLowLevelClick(Sender: TObject);
var
  AFiles : TStringDynArray;
  fh : THandle;
  fl, fr : LongWord;
  i : integer;
  Buffer : Pointer;
begin
  Watch.Start;
  AFiles := TDirectory.GetFiles(FDir);
  Watch.Stop;
  Prot('GetFiles', Watch);
  Watch.Reset;
  for i := Low(AFiles) to High(AFiles) do
  begin
    Watch.Start;
    fh := CreateFile(PChar(AFiles[i]), GENERIC_READ,
      FILE_SHARE_READ, nil, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN, 0); // Optimiert für nur vorwärts lesen
    fl := GetFileSize(fh, nil);
    fr := 0;
    GetMem(Buffer, fl);
    ZeroMemory(Buffer, fl);
    ReadFile(fh, Buffer^, fl, fr, nil);
    CloseHandle(fh);
    FreeMem(Buffer);
    Watch.Stop;
  end;
  Prot('Lowlevel open', Watch);
  Watch.Reset;
end;
Damit liest er die 5000 Dateien in 160-400 ms komplett ein, auch wenn diese gerade frisch erzeugt wurden.

himitsu 30. Apr 2014 15:52

AW: Viele Dateien performant einlesen
 
Zitat:

auch wenn diese gerade frisch erzeugt wurden.
Gerade da liegen die ja vermutlich immernoch im Cache.

Zitat:

Optimiert für nur vorwärts lesen
Zum Glück hatte ich das nicht gleich am Anfang erwähnt. :angel:


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:07 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