Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Dateien schreiben Buffergröße optimieren (https://www.delphipraxis.net/81686-dateien-schreiben-buffergroesse-optimieren.html)

Luckie 1. Dez 2006 16:20


Dateien schreiben Buffergröße optimieren
 
Ich bin gerade dabei meinen FileSplitter zu optimieren. Offensichtlich wird er eingesetzt, um sehr große Dateien zu teilen, um sie auf DVDs brennen zu können.

Delphi-Quellcode:
function SplitFile(Filename, DestFolder: string; SplitSize, CntParts: Int64): Int64;
const
  //BLOCKSIZE        = 1048544;
  BLOCKSIZE        = 32767;
resourcestring
  ChangeDiskMsg    = 'Legen sie die nächste Diskette in Laufwerk a: ein.';
var
  hFile            : THandle;
  SizeOfFile       : Int64; // >4GB
  hPart            : THandle;
  Loop             : Cardinal;
  Partname         : string;
  MemBuffer        : array[0..BLOCKSIZE - 1] of Byte;
  BytesToRead, BytesRead, BytesWritten: Int64;
  TotalBytesRead, OverallBytesRead: Int64;
  ProgressCurrent, ProgressOld: Cardinal;
begin
  bRunning := 1;
  OverallBytesRead := 0;
  SizeOfFile := GetFileSize(PChar(Filename));
  hFile := FileOpen(Filename, $0000);
  if hFile <> INVALID_HANDLE_VALUE then
  begin
    for Loop := 1 to CntParts do
    begin
      // Reset variables
      TotalBytesRead := 0;
      ProgressOld := 0;
      BytesToRead := SplitSize;
      // build filename of the parts
      Partname := DestFolder + '\' + ExtractFilename(Filename) + Format('.%3.3d', [Loop]);
      if FileExists(Partname) then
        DeleteFile(PChar(Partname));
      hPart := FileCreate(Partname);
      if hPart <> INVALID_HANDLE_VALUE then
      begin
        repeat
          BytesRead := FileRead(hFile, MemBuffer, Min(sizeof(MemBuffer), BytesToRead));
          BytesWritten := FileWrite(hPart, MemBuffer, BytesRead);
          Dec(BytesToRead, sizeof(MemBuffer));
          // progress stuff ////////////////////////////////////////////////////
          TotalBytesRead := TotalBytesRead + BytesWritten;
          OverallBytesRead := OverallBytesRead + BytesWritten;
          ProgressCurrent := (OverallBytesRead * 100) div SizeOfFile;
          if ProgressCurrent <> ProgressOld then
          begin
            ProgressOld := ProgressCurrent;

          end;
          SendMessage(hApp, FSM_PROGRESS, ProgressCurrent, Integer(PChar(Partname)));
          //////////////////////////////////////////////////////////////////////
        until (BytesToRead <= 0) or (bRunning = 0);
      end;
      FileClose(hPart);
      if bRunning = 0 then
        Break;
      if (DestFolder = 'a:\') and (Loop < CntParts) then
      begin
        if MessageBox(0, PChar(ChangeDiskMsg), APPNAME, MB_ICONINFORMATION or MB_OKCANCEL) = ID_CANCEL then
        begin
          bRunning := 0;
          break;
        end;
      end;
    end;
    FileClose(hFile);
  end;
  SendMessage(hApp, FSM_FINISH, 0, GetLastError());
  result := GetLastError();
end;
Das ist meiner bisherige Routine. Jetzt wollte ich die Blockgröße hochsetzen, um das ganze etwas zu beschleunigen. Allerdings bekomme ich bei einer Blockgröße von 1048544 den Laufzeitfehler 202: EStackOverflow. Wie kann ich den vermeiden und trotzdem eine angemessene Blockgröße benutzen? Oder wie kann ich die optimale Blockgröße ermitteln? Oder kann man meine Routine noch an anderen Stellen optimieren?

SirThornberry 1. Dez 2006 16:24

Re: Dateien schreiben Buffergröße optimieren
 
einfach nicht auf dem Stack ablegen sondern den Speicher dynamisch anfordern.

Luckie 1. Dez 2006 16:26

Re: Dateien schreiben Buffergröße optimieren
 
Wo lege ich denn was auf den Stack an und wie lege ich es auf dem Heap an?

SirThornberry 1. Dez 2006 16:34

Re: Dateien schreiben Buffergröße optimieren
 
auf dem Stack wird das abgelegt was statich zur Procedure/Methode/Funktion gehört. Auf dem Heap wird der Speicher abgelegt der mit new, StrAlloc, GetMem etc. angefordert wird. Ein dynamisches Array würd also auch schon Abhilfe schaffen da bei SetLength dynamisch Speicher angefordert wird und nur der Pointer auf dem Stack landet.

derzeit legst du hier:
Delphi-Quellcode:
MemBuffer        : array[0..BLOCKSIZE - 1] of Byte;
BLOCKSIZE Bytes auf dem Stack ab beim aufrufen der Funktion.

Bei meinem BDS steht die MaxStackSize zum beispiel per Default auf 100000 womit es in Konflikt mit deinen 1048544 kommen würde

Luckie 1. Dez 2006 16:43

Re: Dateien schreiben Buffergröße optimieren
 
Hm. OK. Aber so:
Delphi-Quellcode:
function SplitFile(Filename, DestFolder: string; SplitSize, CntParts: Int64): Integer;
const
  BLOCKSIZE        = 4194304;
  //BLOCKSIZE        = 32767;
resourcestring
  ChangeDiskMsg    = 'Legen sie die nächste Diskette in Laufwerk a: ein.';
var
  hFile            : THandle;
  SizeOfFile       : Int64; // >4GB
  hPart            : THandle;
  Loop             : Cardinal;
  Partname         : string;
  MemBuffer        : array of Byte;
//  MemBuffer        : array[0..BLOCKSIZE - 1] of Byte;
  BytesToRead, BytesRead, BytesWritten: Int64;
  TotalBytesRead, OverallBytesRead: Int64;
  ProgressCurrent, ProgressOld: Cardinal;
begin
  SetLength(MemBuffer, BLOCKSIZE - 1);
Schreibt er zwar sehr schnell aber in sehr kleinen Happen (Titelleiste flimmert, weil sie ständig aktualisiert wird). Und ist im Endeffekt auch nicht schneller.

Edit:
Er schreibt gar nicht, weil BytesRead immer -1 ist. Auch wenn ich hier dann schreibe:
Delphi-Quellcode:
BytesRead := FileRead(hFile, MemBuffer, Min(length(MemBuffer), BytesToRead));

alzaimar 1. Dez 2006 17:08

Re: Dateien schreiben Buffergröße optimieren
 
Hi Michael,

Ich glaube, Scatter/Gather I/O könnte helfen. Allerdings muss man etwas fummeln, bis es funktioniert. Scatter/Gather wird vom SQL-Server benutzt. Ok, er verwendet 8kb Seiten, sodaß es sein kann, das diese Methode nicht schneller ist, als Deine, aber ist es nicht sowieso so, das FileRead/Write auf 8k Seiten abgebildet wird?

Wenn du Scatter/Gather verwendest, optimierst Du auf der untersten Ebene: Es werden dann -glaube ich- immer 8x8kb Seiten parallel(!) gelesen/geschrieben. Die Doku hierzu ist etwas dünn, Du must also etwas suchen.

Ich meine auch, gelesen zu haben, das Du Systempages alloziieren solltest,zumindest beim S/G I/O.

bigg 1. Dez 2006 17:17

Re: Dateien schreiben Buffergröße optimieren
 
Hi,

es reicht imho kleinere Häppchen auszulesen und diese dann zu verarbeiten, da die gängstigen Speichermedien
auf direkten Wege auch nur kleine Happen lesen bzw. schreiben. (Stichwort: Cluster/Sektoren)

Das Flimmern der Objekte kann man unterbinden, indem man die Komponenten in einem von dir festgelegten Intervall "aktualisiert". Dazu müsstest du ein paar Zeitmessungen durchführen und dann etscheiden, was passieren soll.

Der_Unwissende 1. Dez 2006 17:19

Re: Dateien schreiben Buffergröße optimieren
 
Zitat:

Zitat von Luckie
Er schreibt gar nicht, weil BytesRead immer -1 ist. Auch wenn ich hier dann schreibe:
Delphi-Quellcode:
BytesRead := FileRead(hFile, MemBuffer, Min(length(MemBuffer), BytesToRead));

Hi,
dass er hier nichts sinnvolles schreibt liegt dann am unterschied zwischen dyn. und statischen Arrays. Statt MemBuffer solltest du immer das erste Element (MemBuffer[0]) bzw. dessen Adresse übergeben (je nachdem was benötigt wird). Bei den statischen Arrays steht der Typ (und damit die Länge) ja schon zur Zeit der Übersetzung fest. Bei den dyn. hast du hingegen wieder einen Aufbau, der dem von Strings entspricht (Längenangabe + Daten).

Gruß Der Unwissende

Luckie 1. Dez 2006 17:27

Re: Dateien schreiben Buffergröße optimieren
 
Zitat:

Zitat von alzaimar
Ich glaube, Scatter/Gather I/O könnte helfen.

Danach habe ich auch schon geguckt, nur noch nichts brauchbares gefunden. Hast du das schon mal gemacht?

@Der_Unwissende: Danke, das habe ich mal wieder übersehen. ;)

alzaimar 1. Dez 2006 17:38

Re: Dateien schreiben Buffergröße optimieren
 
Ja, vor Jahren.

Ich bin damals bei Microsoft fündig geworden, dort ist/war es ganz gut erklärt. Denn jetzt ist dort nix mehr zu finden, jedenfalls nicht auf die Schnelle.

Prinzipiell musst Du system pages alloziieren. Die Größe ist systemabhängig, steht aber irgendwo. Dann bastelst Du dir ein Array (steht in der Beschreibung), wo du also 8 Records definerst. In jedem Record steht die Seitenadresse, sowie der ByteOffset in der Datei (int64). Dann machst Du overlapped I/O (soweit ich mich erinnere).

Die API-Routinen sind "ReadFileGather" und "WriteFileScatter"
Links (Auf die Schnelle):
http://www.ebook-pal.com/242-1-144-3...201700476.aspx

http://www.chem.hope.edu/cgi-bin/info2www.cgi?(libc)Scatter-Gather

himitsu 1. Dez 2006 17:38

Re: Dateien schreiben Buffergröße optimieren
 
für den Stack ... den hättest du ja auch einfach vergrößern können (mit {$M... glaub'sch) , aber ich denke mal der winzige Geschwindigkeitsvorteil, bei der Speicherreservierung, gegenüber von GetMem und Co. dürfte bei dir kaum auffallen, da's ja nur einmal (für eine Datei) benötigt wird.

Nach meinen Tests sind 0,5 bis 1 MB völlig ausreichend, bei mehr wird's oftmals eh wieder langsamer. Jedenfalls bei großen und kleinen Dateien gemischt, man könnte höchstens noch die Buffergröße an die Dateigröße anpassen :stupid:



Was du aber noch machen könntest wäre ohne FileCache direkt auszulesen und zu schreiben, vorallem bei (sehr) großen Dateien bringt das recht viel, allerdings mußt du dann auf die Sectorgröße aufpassen, da nur ganze Sektoren ausgelesen werden können.

MSDN-Library durchsuchenCreateFile, FILE_FLAG_NO_BUFFERING


Oder da du eh schön der Reihe nach die Dateien abarbeitest sollte (angeblich, laut MS) FILE_FLAG_SEQUENTIAL_SCAN etwas helfen (theoretisch sollte es aber zusammen mit FILE_FLAG_NO_BUFFERING keine Auswirkungen haben)

Und für's Schreiben dann das FILE_FLAG_SEQUENTIAL_SCAN mit FILE_FLAG_WRITE_THROUGH kombinieren.


Von Scatter/Gather hab ich zwar och mal was gehört, aber mir war so, als wenn negaH dort meinte es würde eh kaum, wenn überhaupt Vorteile bringen (könnte sein, daß darüber was im Forum rumschwirrt)

[add]
Im PSDK steht jedenfalls nicht viel dazu, hatte mich da gestern zufällig mal durch die Dateifunktionen gewurschtelt.


Aber ich bin mir relativ sicher, daß Hagen mal ein Wort darüber hier verloren hatte :angel:

Luckie 1. Dez 2006 17:43

Re: Dateien schreiben Buffergröße optimieren
 
Zur Zeit benutze ich die API Funktionen FileRead und FileWrite. Ich werde erstmal eine Blockgröße ausprobieren, die der Seitengröße entspricht. Und dann eventuell auf CreateFile und den genannten Flags umstellen.

Luckie 2. Dez 2006 15:19

Re: Dateien schreiben Buffergröße optimieren
 
ALSO:

Ich benutze jetzt zum Lesen:
Delphi-Quellcode:
hFile := CreateFile(PChar(Filename), GENERIC_READ , FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL
        or FILE_FLAG_NO_BUFFERING, 0);
und zum Schreiben:
Delphi-Quellcode:
hPart := CreateFile(PChar(Partname), GENERIC_WRITE , FILE_SHARE_WRITE, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL
        or FILE_FLAG_SEQUENTIAL_SCAN or FILE_FLAG_WRITE_THROUGH, 0);
Das hat aber auch noch keine signifikanten Geschwindkeitsgewinne gebracht.

Jetzt experimentiere ich mit der Blockgröße. Allerdings, wenn ich ein dynamisches Array of Byte benutze und schreibe:
Delphi-Quellcode:
BytesRead := FileRead(hFile, MemBuffer[0], Min(sizeof(MemBuffer), BytesToRead));
BytesWritten := FileWrite(hPart, MemBuffer[0], BytesRead);
Delphi-Quellcode:
////////////////////////////////////////////////////////////////////////////////
// Procedure : FileRead
// Comment  : Reads the given amount of bytes into a buffer
function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer;
begin
  if not ReadFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
    Result := -1;
end;

////////////////////////////////////////////////////////////////////////////////
// Procedure : FileWrite
// Comment  : Writes the buffer into the file
function FileWrite(Handle: Integer; const Buffer; Count: LongWord): Integer;
begin
  if not WriteFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
    Result := -1;
end;
Ist BytesRead immer -1. Obwohl ich ihm das ertse Arrayelement angegeben habe. Was mache ich da noch falsch?

Nachtrag: Wenn ich FILE_FLAG_NO_BUFFERING nicht setze geht es. Ist allerdings sau langsam, weil er wohl immer nur 4 Byte liest.

Und lesen und schreiben mit
Delphi-Quellcode:
FILE_FLAG_NO_BUFFERING or FILE_FLAG_SEQUENTIAL_SCAN or FILE_FLAG_WRITE_THROUGH
bringt auch anscheinend nicht wirklich was bei einer Blockgröße von 8KB.

bigg 2. Dez 2006 16:08

Re: Dateien schreiben Buffergröße optimieren
 
Hast du es schonmal mit den Clustergrößen probiert?

Luckie 2. Dez 2006 16:14

Re: Dateien schreiben Buffergröße optimieren
 
Also Blockgröße = Clustergröße? Nein, noch nicht. Aber das ist eine gute Idee. Das wären nur 512 Byte in meinem Fall. Ist das nicht etwas klein? Eventuell soolte man ein Vielfaches davon nehmen?

Und von welcher Partition soll ich die Clustergröße nehmen? Von der ich lese oder auf die ich schreiebe?

Dann müsste ich auch wieder mit einem dynamischen Array arbeiten, aber damit habe ich ja Probleme. :(

himitsu 2. Dez 2006 16:27

Re: Dateien schreiben Buffergröße optimieren
 
Schau dir mal GetDiskFreeSpace an.
> SectorsPerCluster und BytesPerSector

Also ich nemhe mir da ein Vielfaches beider Dateien (die zu Lesende und der zu Schreibenden)

Mit ein bissl Mathe kann man diese Größen (da sie ja 2er-Potenzen entsprechen sollten) ganz prktisch per AND/OR kombinieren.

Delphi-Quellcode:
Size := -(-SizeRead and -SizeWrite and -1048576);
i hoff es stimmt ... ergeben sollte es das kleineste gemeinsame Vielfache von SizeRead, SizeWrite und 1 MB


[edit]
and, nicht or -.-''

Luckie 2. Dez 2006 16:35

Re: Dateien schreiben Buffergröße optimieren
 
Was ist SizeRead und SizeWrite?
SizeRead = Clustergröße zu lesende Datei?
SizeWrite = Clustergröße zu schreibende Datei?

himitsu 2. Dez 2006 16:37

Re: Dateien schreiben Buffergröße optimieren
 
jupp

PS: hab dat OR ma ausgetauscht ._. (siehe oben)

Chewie 2. Dez 2006 16:44

Re: Dateien schreiben Buffergröße optimieren
 
Zitat:

Zitat von Luckie
Ist allerdings sau langsam, weil er wohl immer nur 4 Byte liest.

Klar, du liest ja auch nur 4 Bytes aus:

Delphi-Quellcode:
BytesRead := FileRead(hFile, MemBuffer[0], Min(sizeof(MemBuffer), BytesToRead));
Sizeof ist ein Operator, der die Anzahl Bytes zurückgibt, die eine Variable eines bestimmten Typs auf dem Stack belegt. Und das sind bei einem dynamischen Array nun mal 4 Byte, da die Variable ja nur einen Zeiger auf den entsprechenden Speicherbereich im Heap darstellt. Da sollte also eher sowas stehen wie
Delphi-Quellcode:
BytesRead := FileRead(hFile, MemBuffer[0], Min(sizeof(MemBuffer[0] * Length(MemBuffer), BytesToRead));

Luckie 2. Dez 2006 16:46

Re: Dateien schreiben Buffergröße optimieren
 
Zitat:

Zitat von himitsu
PS: hab dat OR ma ausgetauscht ._. (siehe oben)

Jupp. Ich hatte nämlich wieder 512 Byte raus. ;) Jetzt bin ich bei einem MB, wenn die beiden Clustergrößen 512 Byte sind.

Jetzt bin ich wieder bei meinem dynamischen Array mit SetLength und er liest immer -1 Byte:

Delphi-Quellcode:
BytesRead := FileRead(hFile, MemBuffer[0], Min(sizeof(MemBuffer[0]) * length(MemBuffer), BytesToRead));
Delphi-Quellcode:
function FileRead(Handle: Integer; var Buffer; Count: LongWord): Integer;
begin
  if not ReadFile(THandle(Handle), Buffer, Count, LongWord(Result), nil) then
    Result := -1;
end;
:wall:

Nachtrag: var Buffer hat ihn FileRead den Wert no Value. :gruebel:

Luckie 3. Dez 2006 22:17

Re: Dateien schreiben Buffergröße optimieren
 
Ich muss das Thema noch mal nach oben holen. Ich bin da irgendwie nochnicht weitergekommen. GetLastError liefert mir übrigens "Falscher Parameter" habe ich jetzt rausgefunden.

Habs: Man dar bei dynamischen Arrays nicht den Flag FILE_NO_BUFFERING benutzen. Aber die Flags FILE_FLAG_SEQUENTIAL_SCAN und FILE_FLAG_WRITE_THROUGH haben auch schon einiges an Performance gebracht.


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

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 by Thomas Breitkreuz