Delphi-PRAXiS
Seite 2 von 3     12 3      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi File IO mit dem Windows-API? (https://www.delphipraxis.net/115006-file-io-mit-dem-windows-api.html)

shmia 9. Jun 2008 13:55

Re: File IO mit dem Windows-API?
 
Zitat:

Zitat von HHick123
Was ich bei TFileStream etwas schade finde, ist dass es eine "Eigenintelligenz" bezüglich der Größe der tatsächlich gelesenen Häppchen zu haben scheint, ...

TFileStream reicht Read und Write Aufrufe ziemlich direkt an das Betriebssystem durch. (Also TFileStream puffert nicht )
TFileStream.ReadBuffer -> TFileStream.Read -> FileRead() -> ReadFile() Win-API

Wenn man Byteweise liest, wird man trotz des geringen Overheads einen Leistungsverlust merken.
So ungefähr ab 256 Bytes gibt es kaum noch Unterschiede zu grösseren Blockgrössen.
Hier eine Messung:
Code:
write 1048576 blocks à 1 bytes: 5750 ms
write 524288 blocks à 2 bytes: 2547 ms
write 262144 blocks à 4 bytes: 1266 ms
write 131072 blocks à 8 bytes: 640 ms
write 65536 blocks à 16 bytes: 313 ms
write 32768 blocks à 32 bytes: 172 ms
write 16384 blocks à 64 bytes: 78 ms
write 8192 blocks à 128 bytes: 31 ms
write 4096 blocks à 256 bytes: 31 ms
write 2048 blocks à 512 bytes: 16 ms
write 1024 blocks à 1024 bytes: 0 ms
write 512 blocks à 2048 bytes: 0 ms
write 256 blocks à 4096 bytes: 15 ms
write 128 blocks à 8192 bytes: 0 ms
write 64 blocks à 16384 bytes: 0 ms
write 32 blocks à 32768 bytes: 0 ms
read 1048576 blocks à 1 bytes: 2813 ms
read 524288 blocks à 2 bytes: 1437 ms
read 262144 blocks à 4 bytes: 704 ms
read 131072 blocks à 8 bytes: 343 ms
read 65536 blocks à 16 bytes: 188 ms
read 32768 blocks à 32 bytes: 78 ms
read 16384 blocks à 64 bytes: 47 ms
read 8192 blocks à 128 bytes: 31 ms
read 4096 blocks à 256 bytes: 16 ms
read 2048 blocks à 512 bytes: 0 ms
read 1024 blocks à 1024 bytes: 0 ms
read 512 blocks à 2048 bytes: 15 ms
read 256 blocks à 4096 bytes: 0 ms
read 128 blocks à 8192 bytes: 0 ms
read 64 blocks à 16384 bytes: 0 ms
read 32 blocks à 32768 bytes: 0 ms

HHick123 10. Jun 2008 13:24

Re: File IO mit dem Windows-API?
 
Liste der Anhänge anzeigen (Anzahl: 2)
Hallo Leute,
Also ich bin mittlerweile mit TFileStream auf einen grünen Zweig gekommen.

Mit dem oben angesprochenen Tool "File Monitor" hab' ich die Plattenzugriffe von TFilestream (die tatsächlich zugegriffene "Häppchengroesse") beobachtet und in etwa folgendes gesehen:

- Im großen und ganzen scheint die TFileStream-Buffergrößen als Häppchengröße auf,
dies ist natürlich ein Problem beim sequenziellen lesen/schreiben von Datentypen, die nur wenige byte groß sind.

- Wenn man im Tool "File Monitor" auf "Advanced Output" umschaltet, sieht man ausserdem einen interessanten Effekt: Anscheinend wird zunächst ein direkterer Hardwarezugriff mittels "FASTIO_WRITE" bzw. "FASTIO_READ" versucht, wobei als "Häppchengröße" die TFileStream-Buffergröße versucht wird. Schlägt dieser fehl, so wird ein Zugriff über "IRP_MJ_WRITE" bzw. "IRP_MJ_READ" nachgeschoben, wobei andere (i.a kleinere) "Häppchengrößen" verwendet werden.

Der Punkt ist nun der: Auf meinem Notebook z.B., sehe ich, dass "FASTIO_READ" auch mit großen "Häppchengrößen" funktioniert. Ab einer Buffergröße von 512kB merke ich aber kaum mehr einen Performance-Gewinn mit TFileStream. Bei noch wesentlich größeren Buffergrößen (die anscheinend dann wirklich zu groß sind) fällt der Mechanismus aber, glaub' ich, wieder auf 65536 byte "Häppchengröße" zurück.

"FASTIO_WRITE" dürfte allerdings - zumindest auf meinem Notebook - nur mit einer Häppchengröße von bis zu 65536 bytes funktionieren. Darüber schlägt es fehl, und es wird "IRP_MJ_WRITE" mit 65536 byte "Häppchengröße" verwendet. Allerdings versucht er beim nächsten Buffer wieder zunächst ein "FASTIO_WRITE", dass wieder fehlschlägt, usw... Das könnte die Sache natürlich ein bischen ausbremsen, obwohl ich nicht weiss, in welcher Größenordnung die Zeit liegt, bis der Fehlschlag von "FASTIO_WRITE" feststeht.

Zusammengefasst hab ich den Eindruck, dass bei meinem Notebook die Performance beim Lesen ab einer TFileStream-Buffergröße von 512kB nicht mehr wesentlich zunimmt und die Performance beim Schreiben ab einer TFileStream-Buffergröße von ca. 65536 nicht mehr wesentlich zunimmt, ev. sogar minimal abnimmt. Anbei mein momentaner Code, wie ich singles, smallints und bytes sequenziell von/auf Platte lese/schreibe:

Für Verbesserungen, Bug-Reports, Ideen, wie man den Code schneller machen könnte wäre ich natürlich sehr dankbar!!

Viele Grüße,
Helmut

Apollonius 10. Jun 2008 13:29

Re: File IO mit dem Windows-API?
 
Das FASTIO_WRITE, IRP_MJ_WRITE etc. ist meiner Meinung nach uninteressant, da das arg nach Aktionen auf Treiberebene aussieht. Alles in allem denke ich, dass es Windows bei Standard-IO so gut macht, wie es geht - also keine zusätzliche Pufferung einführen.
Die einzige neue Möglichkeit, die mir noch einfällt, sind Memory Mapped Files - Neutral General hat hier mal eine Unit gepostet, die entsprechende TStream-Nachfahren einführt.

himitsu 10. Jun 2008 13:38

Re: File IO mit dem Windows-API?
 
MMFs würde ich in diesem Zusammenhang nicht unbedingt als Optimierung ansehn ... bei denen sind die Zugriffe noch unberechenbarer, als bei "Standard"-FileIOs.




MSDN-Library durchsuchenCreateFile mit ReadFile/WriteFile und nahezu alles, was davon abgeleitet ist ... z.B. FileStreams, wo man auch die nötigen Parameter angeben kann, wäre wohl optimal genug (vom Aufwand her).

Mit den Flags MSDN-Library durchsuchenFILE_FLAG_SEQUENTIAL_SCAN und MSDN-Library durchsuchenFILE_FLAG_RANDOM_ACCESS (siehe MSDN-Library durchsuchenCreateFile) kann man Windows auch noch mitteilen, in welcher Art man auf die Datei zugreifen will, damit Windows sein Lese-/Speicher-/Cacheverhalten entsprechend optimaler einstellen kann.


PS:
Zitat:

dass ich die Files in möglichst großen Portionen auf die Platte schreibe
in welcher Größenordnung liegt denn bei dir der Puffer (die Portionen)?


PSS:
Zitat:

... und intensiv auf der Platte herumfuhrwerkt - einerseits soll's schneller gehen, andererseits tut mir die Platte leid)
muß unbedingt die ganze Zeit auf der Platte rumgekramt werden,
oder könnte man Teile komplett im RAM abarbeiten lassen?

HHick123 10. Jun 2008 15:52

Re: File IO mit dem Windows-API?
 
Zitat:

Zitat von Apollonius
Alles in allem denke ich, dass es Windows bei Standard-IO so gut macht, wie es geht - also keine zusätzliche Pufferung einführen.

Das wäre natürlich ein Argument, von TFileStream abzuweichen und direkt auf das Windows-API aufzusetzen. Ehrlich gesagt, war ich bisher einfach zu faul, mich diesbezüglich in die msdn einzulesen.

Zitat:

Zitat von himitsu
Mit den Flags FILE_FLAG_SEQUENTIAL_SCAN und FILE_FLAG_RANDOM_ACCESS (siehe CreateFile ) kann man Windows auch noch mitteilen, in welcher Art man auf die Datei zugreifen will, damit Windows sein Lese-/Speicher-/Cacheverhalten entsprechend optimaler einstellen kann.

Das klingt natürlich sehr interessant!

Zitat:

Zitat von himitsu
in welcher Größenordnung liegt denn bei dir der Puffer (die Portionen)?

Von den Algorithmen her möchte ich einfach sequenziell "file of single, file of byte, file of smallint" lesen beziehungsweise schreiben.

Zum Filezugriff verwende ich momentan TFileStream auf einen Lesebuffer von 512kB und
einen Schreibbuffer von 64kB, aus denen ich die singles/bytes/smallints
sequenziell heraushole/hineinschreibe. Zu Beginn dieses Threads hab' ich noch kleinere Buffer (32kB) benutzt, hatte anstatt TFileStream: BlockWrite AssignFile und Co. in Verwendung, und hatte ausserdem noch laufend unnötige Aufrufe von SetFileAttributes und ForceDirectories. Es wird' schon besser :-D

Zitat:

Zitat von himitsu
muß unbedingt die ganze Zeit auf der Platte rumgekramt werden,
oder könnte man Teile komplett im RAM abarbeiten lassen?

Die Daten haben z.B. eine insgesamte Größe von etwa 50GB, momentan seh' ich da nicht viel Chancen, denn sie werden in mehreren "passes" analysiert, d.h. die 50GB werden nach einem Berechnungsschritt wieder komplett für den nächsten Schritt benötigt.
Teilweise sind es sehr lange File (stundenlange Audioaufnahmen, die abgespeichert oder aber analysiert werden wollen), teilweise auch viele kleine Files (etwa 40kB)...

Viele Grüße,
Helmut

shmia 10. Jun 2008 16:24

Re: File IO mit dem Windows-API?
 
Zitat:

Zitat von HHick123
Das wäre natürlich ein Argument, von TFileStream abzuweichen und direkt auf das Windows-API aufzusetzen.

TFileStream puffert nicht, die Daten werden direkt an die Windows-API durchgereicht!
Hier die Messungen:
Zitat:

Windows API: write 1048576 blocks à 1 bytes: 5015 ms
TFileStream: write 1048576 blocks à 1 bytes: 5047 ms
Der Unterschied beträgt ganze 32ms, wenn man eine Datei mit einem MB byteweise schreibt.
Das waren über eine Million Schreibvorgänge.
Wenn man den optimalen Durchsatz erreichen möchte, dann muss man selbst (*) puffern und eine Puffergrösse von ~ 2 KB bereithalten.
Den Rest erledigt der Cache von Windows.

*) Es gibt schon gepufferte Streamklassen im Internet. Bei Google suchenTBufferedstream AND delphi

HHick123 10. Jun 2008 16:53

Re: File IO mit dem Windows-API?
 
Zitat:

TFileStream puffert nicht, die Daten werden direkt an die Windows-API durchgereicht! Hier die Messungen:

Zitat:

Windows API: write 1048576 blocks à 1 bytes: 5015 ms
TFileStream: write 1048576 blocks à 1 bytes: 5047 ms

Also dann sehe ich für den Moment mal eine "selbstgepufferte-TFileStream-Variante" als vorläufige Lösung an (ungefähr so, wie ich sie oben als Datei angehängt habe)...

Meinst Du, ob man bei einer der "Windows API-Variante" mit diesem FLAG_FILE_SEQUENTIAL_SCAN von CreateProcess, das himitsu erwähnt hat, oder viellicht anderen Flags noch wesentliche Optimierungen zustandebringen könnte, die TFileStream nicht ermöglicht und so eine "selbstgepufferte-WindowsAPI-Variante" vielleicht Vorteile gegenüber der "selbstgepufferten-TFilestream-Variante" hätte?

Viel Grüße und vielen Dank für die bisherigen Analysen!
Helmut

shmia 10. Jun 2008 17:57

Re: File IO mit dem Windows-API?
 
Zitat:

Zitat von HHick123
Meinst Du, ob man bei einer der "Windows API-Variante" mit diesem FLAG_FILE_SEQUENTIAL_SCAN von CreateProcess, das himitsu erwähnt hat, oder viellicht anderen Flags noch wesentliche Optimierungen ...

Das könnte durchaus etwas bringen.
Dazu würde ich von der Klasse THandleStream ableiten:
Delphi-Quellcode:
TSeqFileStream = class(THandleStream)
public
  constructor Create(const FileName: string; Mode: Word);
  destructor Destroy; override;
end;

constructor TSeqFileStream.Create(const FileName: string; Mode: Word);
begin
  if Mode = fmCreate then
  begin
    FHandle := CreateFile(PChar(FileName), GENERIC_READ or GENERIC_WRITE, .....); // CreateFile aus der Windows-API ***
    if FHandle < 0 then
      raise EFCreateError.CreateResFmt(@SFCreateError, [FileName]);
  end else
  begin
    FHandle := CreateFile(PChar(FileName), .....); // CreateFile aus der Windows-API  ***
    if FHandle < 0 then
      raise EFOpenError.CreateResFmt(@SFOpenError, [FileName]);
  end;
end;

destructor TFileStream.Destroy;
begin
  if FHandle >= 0 then FileClose(FHandle);
end;
Die beiden Aufrufe für CreateFile() wären noch zu programmieren. Das wird eine kleine Parameterschlacht, weil es hier so viele Möglichkeiten gibt.

himitsu 11. Jun 2008 12:37

Re: File IO mit dem Windows-API?
 
Zitat:

Zitat von shmia
Wenn man den optimalen Durchsatz erreichen möchte, dann muss man selbst (*) puffern und eine Puffergrösse von ~ 2 KB bereithalten.

am optimalsten kommt man wohl eher, wenn man die WindowsCache umgeht und selbst einen passenden Puffer einrichtet, aber da müßte man ganz genau darauf achten, wie groß der Puffer ist, da er dann an die Sektorgröße des Datenträgers angepaßt werden muß (und am Besten noch auf die Größen von z.B. Festplatttencache und Co.) und wenn man dann auch noch dafür sorgt, daß der eigene Cachespeicher nicht in die Fänge der Auslagerungsdatei gerät ... also nicht grad einfach (ich spiele ja nicht umsonst, schon seit sehr langer Zeit, mit meinem FileSplitter rum)

für eine eigene Puffergröße macht sich 16/32/64 KB wohl schon recht optimal (jedenfalls sollten es immer ganze Vielfache von 512 Byte und bei CDs/DVDs 2 KB sein) ... unter 8 KB würde ich bei den Cache und Clustergrößen aktueller Hardware und bei den großen Dateien nicht gehn ... und mehr als 8 MB wirken sich oftmals auch wieder als nachteilig aus.


Also abgesehn von den kleinen Dateien macht sich anscheinend das direkte arbeiten mit der WinAPI (oder halt der kleine Umweg über shmias TSeqFileStream) recht gut.
Ich würde jetzt aber erstmal keine extrabehandlung für die paar kleinen Dateichen vorsehn.

HHick123 11. Jun 2008 16:08

Re: File IO mit dem Windows-API?
 
Zitat:

Zitat von shmia
Die beiden Aufrufe für CreateFile() wären noch zu programmieren. Das wird eine kleine Parameterschlacht, weil es hier so viele Möglichkeiten gibt.

Hallo Leute, ich hab' mal versucht, diesen Vorschlag (Ableiten von THandleStream) etwas zu konkretisieren. Ich würde ich mir das ungefähr so vorstellen:
Delphi-Quellcode:
program a;

uses
  Classes, Windows, SysUtils, RTLConsts;

type
  TSeqFileStream=class(THandleStream)
public
  constructor Create(const FileName: string; Mode: Word);
  destructor Destroy; override;
end;

constructor TSeqFileStream.Create(const FileName: string; Mode: Word);
const
  AccessMode: array[0..2] of LongWord=
  (GENERIC_READ,GENERIC_WRITE,GENERIC_READ or GENERIC_WRITE);
  ShareMode: array[0..4] of LongWord=
  (0,0,FILE_SHARE_READ,FILE_SHARE_WRITE,FILE_SHARE_READ or FILE_SHARE_WRITE);
var
  fo:integer;
begin
  if Mode=fmCreate then
  begin
    fo:=CreateFile(PChar(FileName),GENERIC_READ or GENERIC_WRITE, 0, nil,
    CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN {<--!!}, 0);
    inherited Create(fo);
    if FHandle<0 then raise EFCreateError.CreateResFmt(@SFCreateError, [FileName]);
  end
  else
  begin
    fo:=-1;
    if ((Mode and 3)<=fmOpenReadWrite)
    and ((Mode and $F0)<=fmShareDenyNone) then
    fo:=CreateFile(PChar(FileName),
    AccessMode[Mode and 3], ShareMode[(Mode and $F0) shr 4], nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL or FILE_FLAG_SEQUENTIAL_SCAN {<--!!}, 0);
    inherited Create(fo);
    if FHandle<0 then raise EFOpenError.CreateResFmt(@SFOpenError, [FileName])
  end
end;

destructor TSeqFileStream.Destroy;
begin
  if FHandle>=0 then FileClose(FHandle);
  inherited Destroy;
end;

end.
Den Parameterkrieg hab' ich mich mal an TFileStream gehalten und FILE_FLAG_SEQUENTIAL_SCAN hinzugefügt, aber es ist wirklich wild!! Eine simples "case Mode of" zur Parameterkonvertierung hätte ich eigentlich schöner gefunden... Leider komm' ich heute nicht mehr zum Testen.

Viele Grüße,
Helmut


Alle Zeitangaben in WEZ +1. Es ist jetzt 06:36 Uhr.
Seite 2 von 3     12 3      

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