Delphi-PRAXiS
Seite 3 von 4     123 4      

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)

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;


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:33 Uhr.
Seite 3 von 4     123 4      

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