Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Neuen Beitrag zur Code-Library hinzufügen (https://www.delphipraxis.net/33-neuen-beitrag-zur-code-library-hinzufuegen/)
-   -   Delphi TBufferedFileStream (https://www.delphipraxis.net/140248-tbufferedfilestream.html)

Laufi 14. Sep 2009 14:16


TBufferedFileStream
 
Hallo!

Ich muss manchmal ziemlich kleine daten von grossen dateien einlesen und der FileStream von Delphi ist manchmal etwas lahm deshalb habe ich versucht es ein wenig zu verbessern :shock:

Ich habe es TBufferedFileStream getauft, weil es einfach ein bisschen mehr buffert ohne dass man etwas machen muss. Es ist eigentlich immer schneller, vorallem bei kleineren daten also unter 32 bytes oder so ist es 5.5 mal schneller beim lesen, beim schreiben bis zu 7 mal schneller. Es kann ganz einfach verwendet werden, einfach in deinem Code überall TBufferedFileStream anstatt nur TFileStream schreiben :roll:

Delphi-Quellcode:
unit BufferedFileStream;

interface

uses
  Classes;

type
  TBufferedFileStream = class(TFileStream)
  const
    MaxBufSize = 4096;
  private
    FBuffer: array[0..Pred(MaxBufSize)] of Byte;
    FBufOffset: Int64;
    FBufSize: Integer;
    FDirty: Boolean;
  protected
    procedure MoveBuffer(const NewOffset: Int64); virtual;
    procedure FlushBuffer; virtual;
  public
    destructor Destroy; override;
    function Read(var Buffer; Count: Longint): Longint; override;
    function Write(const Buffer; Count: Longint): Longint; override;
  end;

implementation

{ TBufferedFileStream }

destructor TBufferedFileStream.Destroy;
begin
  FlushBuffer;
  inherited;
end;

procedure TBufferedFileStream.MoveBuffer(const NewOffset: Int64);
begin
  FlushBuffer;
  FBufOffset:= NewOffset;
  Seek(FBufOffset, soBeginning);
  FBufSize:= inherited Read(FBuffer, SizeOf(FBuffer));
end;

procedure TBufferedFileStream.FlushBuffer;
begin
  if FDirty then
  begin
    Seek(FBufOffset, soBeginning);
    inherited Write(FBuffer, FBufSize);
    FDirty:= False;
  end;
end;

function TBufferedFileStream.Read(var Buffer; Count: Integer): Longint;
var
  Offset: Int64;
  Delta: Integer;
begin
  if Count < MaxBufSize then
  begin
    Offset:= Seek(0, soCurrent);
    Delta:= Offset - FBufOffset;
    if (Delta < 0) or (Delta + Count > FBufSize) then
    begin
      MoveBuffer(Offset);
      Delta:= Offset - FBufOffset;
    end;
    Result:= FBufSize - Delta;
    if Result > Count then
      Result:= Count;
    if Result > 0 then
    begin
      Move(FBuffer[Delta], Buffer, Result);
    end;
    Seek(Offset + Result, soBeginning);
  end else
    Result:= inherited Read(Buffer, Count);
end;

function TBufferedFileStream.Write(const Buffer; Count: Integer): Longint;
var
  Offset: Int64;
  Delta: Integer;
begin
  if Count < MaxBufSize then
  begin
    Offset:= Seek(0, soCurrent);
    Delta:= Offset - FBufOffset;
    if (Delta < 0) or (Delta + Count > MaxBufSize) then
    begin
      MoveBuffer(Offset);
      Delta:= Offset - FBufOffset;;
    end;
    Result:= Count;
    if FBufSize < Delta + Count then
      FBufSize:= Delta + Count;
    if Result > 0 then
    begin
      Move(Buffer, FBuffer[Offset - FBufOffset], Result);
      FDirty:= True;
    end;
    Seek(Offset + Result, soBeginning);
  end else
    Result:= inherited Write(Buffer, Count);
end;

end.
ich empfehle das allen, die für schnelle Streams aus dateien nicht selber einen Buffer holen möchten :pale:

Liebe Grüsse
Laufi

himitsu 14. Sep 2009 15:13

Re: TBufferedFileStream
 
Du weißt aber, daß du jetzt mindestens 3 Cache in deinem Stream drinnen hast?

- dein Puffer
- die WindowsFileCache
- die Cache z.B. der Festplatte
- .........

es ginge also noch etwas Flotter
- entweder man nutzt die WFC besser aus und optimiert deren Verwaltung
- oder man umgeht die WFC und nutzt auf der tieferen Ebene nur noch die eigene Cache.


für ersteren Weg (Optimieren der WFC ... siehe MSDN-Library durchsuchenCreateFile) hab ich mich in himXML entschieden, da man die Lese-/Schreibzugriffe für den anderen Weg (umgehen der WFC) etwas mehr koordiniert werden muß (dieses findet man aber im FileSplitter wieder, da dort der Datentransfer sehr gut vorhersehbar ist)


Zitat:

Zitat von Laufi
ich empfehle das allen, die für schnelle Streams aus dateien nicht selber einen Buffer holen möchten :pale:

PS: die alten Pascalfunktionen um Delphi-Referenz durchsuchenAssginFile
nutzen einen Puffer, nur ist der leider standardmäßig sehr suboptimal eingestellt (128 Byte), so daß er in diesem Fall eher bremst ... es sei den man gibt da einen eigenen "passerenden" Puffer an


PSS: wenn du den Stream entweder nur zum Lesen oder Schreiben öffnen läßt, dann ließe sich die Pufferverwaltung wesenlich vereinfachen und es wären auch keine/weniger SEEKs nötig

Mithrandir 14. Sep 2009 15:24

Re: TBufferedFileStream
 
An dieser Stelle sollte man zur Freude des Autors vielleicht nochmal erwähnen, dass die Jedis auch so einen gepufferten Stream haben. :mrgreen:

FAlter 14. Sep 2009 16:00

Re: TBufferedFileStream
 
Hi,

Zitat:

Zitat von himitsu
- dein Puffer
- die WindowsFileCache
- die Cache z.B. der Festplatte
- .........

Durch Setzen der MaxBufSize auf 64KiB oder größer schaltet man afaik den Windows-Cache für diesen Lesevorgang aus. 64 KiB ($10000) sollte der optimale Wert sein.

Definitiv ist ein Cache innerhalb des eigenen Programmes schneller als der von Windows.

Für kleinere Dateien wäre es auch denkbar, einen TMemoryStream zu verwenden und erst per LoadFromFile zu laden und später per SaveToFile zurück zu schreiben.

Ansonsten auch MMF: http://www.delphipraxis.net/internal...t.php?t=134059

Gruß
FAlter

Laufi 14. Sep 2009 17:14

Re: TBufferedFileStream
 
Hallo!

Zitat:

Zitat von himitsu
Optimieren der WFC ... siehe MSDN-Library durchsuchenCreateFile)

Meinst du wenn man da sagt sequential scan oder random access? Ich habe das mal probiert, aber habe fast nichts gemerkt :?

Zitat:

Zitat von himitsu
PSS: wenn du den Stream entweder nur zum Lesen oder Schreiben öffnen läßt, dann ließe sich die Pufferverwaltung wesenlich vereinfachen und es wären auch keine/weniger SEEKs nötig

wie meinst du das? ich sehe nur dass man es noch ein bisschen besser machen kann wenn man nur schreibt aber das macht man doch fast nie :oops:

Liebe Grüsse
Laufi

himitsu 14. Sep 2009 17:35

Re: TBufferedFileStream
 
Der TXMLReadWriteBuffer (himXML.pas) und die zugehörigen Prozeduren werden jeweils nur zum Lesen ODER Schreiben genutzt, also nicht gleichzeitig und da muß man erstmal keine Umschaltung zwischen Beidem einbauen und kann die Funktion geziehlt auf jeweils eines von Beidem optimieren.
Aber ich lese dort entweder die Datei "komplett" ein oder speichere sie nur auf die Platte.

Random- oder Sequential-Access merkt man eigentlich erst sehr stark, wenn wirklich viel gelesen/schrieben wird und dieses nicht alles in die WFC paßt.
Aber es optimiert zumindesent die Speicherverwaltung etwas.

PS: Ich hatte vor ein paar Tagen mal wieder ein Backup gemacht und wenn da das Programm "fehlerhaft" mit der Cache umgeht, dann legt man schonmal Windows minutenlang (und länger) lahm, wenn man ~800 GB mit teilweise über 100 MB/s durch diese Cache durchjagt und insgesammt nur 4 GB RAM zur Verfügung hat.


OK, es hat ja auch Vorteile, wenn man gleichzeitig lesen und schreiben kann, auch wenn ich fast nie soetwas benöige.

Aber egal ob nun gleichzeitig gelesen und geschrieben wird, wird es mit deiner Variante wesentlich langsamer, als nur mit der WFC,
wenn man wirklich mal quer durch die Datei sappt (RandomAccess).

FAlter 14. Sep 2009 17:42

Re: TBufferedFileStream
 
Hi,

Zitat:

Zitat von himitsu
Aber egal ob nun gleichzeitig gelesen und geschrieben wird, wird es mit deiner Variante wesentlich langsamer, als nur mit der WFC,
wenn man wirklich mal quer durch die Datei sappt (RandomAccess).

Ich denke in diesem Fall ist ein Cache (fast) immer Kontraproduktiv.

Gruß
Felix

himitsu 14. Sep 2009 17:55

Re: TBufferedFileStream
 
Zitat:

Zitat von FAlter
Ich denke in diesem Fall ist ein Cache (fast) immer Kontraproduktiv.

Das kommt auf den Cache drauf an.

- sein Cache ist es aber wirklich, da er nur je einen Bereich puffert und diesen "größeren" Bereich neu einlesen muß, wenn die neue Position nicht da drinnen liegt,
also im Extremfalle ließt er z.B. bei 32 KB Cache und 16 Byte zum Auslesen, 2048 Mal soviele Daten ein, wie nötig.

- die WindowsFileCache hat mehrere Cachebereiche und demnach kann/wird es vorkommen, daß zufällig schon passende Bereiche geladen sind und es somit nicht jedesmal neu aus der Datei geladen werden müßte.



Drum meinte ich ja, daß es sich mit jeweils nur einer Art (Lesen oder Schreiben) und möglichst sequentiellem Zugriff die eigene Cache sich da besser/leichter optimieren läßt ... vorallem wenn man nur einen Block in dieser Cache hat.

Deep-Sea 16. Sep 2009 14:49

Re: TBufferedFileStream
 
Ich wollte mal einen Fehler melden.
Folgendes Beispiel, bei dem ich zu Testzwecken die Konstante MaxBufSize auf 4 gesetzt habe:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  S: AnsiString;
begin
  With TBufferedFileStream.Create('C:\Test.txt', fmCreate) do
  try
    S := 'ABC';
    Write(S[1], Length(S));
    S := 'Zu lang!';
    Position := 0;
    Write(S[1], Length(S));
  finally
    Free;
  end;
end;
Was steht am Ende in der Datei? Richtig: ABClang!
Denn erst wird ya das 'Zu lang!' in die Datei geschrieben und erst am Ende der Puffer geleert in dem 'ABC' steht.

Klar, man könnte jetzt sagen, wer so'n Müll macht wie ich bei dem Test hat selbst schuld, aber so bin ich halt :-D

igel457 16. Sep 2009 16:36

Re: TBufferedFileStream
 
Man könnte auch gleich das hier verwenden: http://andorra.cvs.sourceforge.net/v....7&view=markup

Besonders die Funktionen "QueryBufferedStream" und "FreeBufferedStream" sind sehr praktisch.

Delphi-Quellcode:
{Speeds up the given stream using the TAdBufferStreamAdapter class. Simply call
 this method and use the stream you want to accelerate as parameter. Calls of
 QueryBufferedStream and FreeBufferedStream may be cascaded.}
procedure QueryBufferedStream(var AStream: TStream);
{Frees a buffered stream and returns the original stream. Calls of
 QueryBufferedStream and FreeBufferedStream may be cascaded.}
procedure FreeBufferedStream(var AStream: TStream);


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