Entwickler-Ecke

Dateizugriff - möglichst schnell einzelne Bytes lesen?


cht - So 08.08.04 12:43
Titel: möglichst schnell einzelne Bytes lesen?
Hallo Leute,
Ich bin auf der Suche nach einer Möglichkeit, möglichst schnell einzelne Bytes aus einer Datei auszulesen. Im Moment lade ich die ganze Datei in einen TMemoryStream, um dann per Seek und ReadBuffer darauf zuzugreifen. Für größere Dateien ist das natürlich nicht sehr schön. Also direkt von der Datei lesen - nur wie? nehme ich einen TFileStream, den Pascal-Typ file oder die Win-API-Routinen? Es geht in erster Linie um die Geschwindigkeit, denn wenn ich bei jedem Seek-Read-Vorgang sagen wir 30µs spare, macht das bei einer 2 MB-Datei schon 1 Minute!
Danke für eure Hilfe

mfG
cht


maxk - So 08.08.04 16:30

Ich verwende gerne Streams, daher kann ich dir TFileStream nur ans Herz legen.


cht - So 08.08.04 17:43

warum benutzt du die denn gerne? nur weil sie einfacher zu proggen sind? ich mach nämlich gerne etwas mehr aufwand um mehr geschwindigkeit rauszuholen.
Danke für deine Antwort

cht


maxk - So 08.08.04 17:47

Ich habe es mal ausprobiert, und da ware der FileStream sehr viel schneller als die TP-Variante. Mit den APIs habe ich es (aus Faulheit) noch nicht probiert. Ich habe aus einer 1 GB Datei ab 500 MB, 500 KB ausgelesen.


Motzi - So 08.08.04 20:18

Also wenn du immer nur einzelne Bytes liest wirst du von der Geschwindigkeit her immer recht niedrig bleiben... warum nicht einen größeren Puffer einlesen und den dann abarbeiten..?

Möglicherweise wären in deinem Fall aber auch MMFs (Memory Mapped Files) angebracht... (weiß ja nicht genau was du vor hast)


BenBE - Mo 09.08.04 22:24

Was er vor hat, würde mich aber auch mal interessieren...

Aber erstmal generell:
KEINE EINZELNEN Bytes lesen, sondern immer Blöcke = x * 512 Bytes (bevorzugt: 1024, 4096, 65536 Bytes).
TMemoryStream bei Files: NO!,
File-I\O-API: Vermeiden, soviel mehr Performance gibt's dabei nicht, Streams reichen schon
@MMFs: Schnellste Möglichkeit, ABER gute Pointer-Kenntnisse sind Voraussetzung, sonst gibt's Endlose AV-Ansammlungen.

Für einzelne Bytes würd ich in seinem Fall mal so tippen, dass sich TFileStream mit einem 64KB Puffer anbietet.


supermuckl - Sa 14.08.04 23:51

Zitat:
ABER gute Pointer-Kenntnisse sind Voraussetzung, sonst gibt's Endlose AV-Ansammlungen

was bitte sind AV-Ansammlungen?

hab ne komponente geschrieben die MMF benutzt und würde gerne wissen ob ich da was übersehen hab was probleme machen könnte


BenBE - So 15.08.04 00:25

supermuckl hat folgendes geschrieben:
Zitat:
ABER gute Pointer-Kenntnisse sind Voraussetzung, sonst gibt's Endlose AV-Ansammlungen
was bitte sind AV-Ansammlungen?

AV ist ne Abkürzung, die für AccessViolation (Zugriffsverletzung) steht.
Naja, und Ansammlungen davon sind diese endlosen Haufen von Meldungen, die du daraufhin kriegst ;-)

Ohne Source gestaltet sich die Fehlersuche etwas kompliziert ;-)


supermuckl - So 15.08.04 21:53

ähm ne ich hab keine fehler.. aber ich will ja nicht das welche kommen ;) deshalb dachte ich hättest du für mich nen tip worauf ich besonderst achten sollte..

AV kommen ja dann wohl wenn ich auf einen speicherbereich zugreife den es nicht gibt ( z.b. länger als das MMF View ist )
sowas hab ich schon von vornherein ausgeschlossen..


BenBE - So 15.08.04 22:49

supermuckl hat folgendes geschrieben:
ähm ne ich hab keine fehler.. aber ich will ja nicht das welche kommen ;) deshalb dachte ich hättest du für mich nen tip worauf ich besonderst achten sollte..

Direkte Sachen, worauf man achten sollte nicht, aber ne Empfehlung:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
type TByteArray = array [0..0of Byte:
    PByteArray = ^TByteArray;

var PBA:PByteArray;

@PBA := MMFData;


Zugriff auf die Einzelbytes dann mit:

PBA^[0..MMFSize-1] (Lesen und Schreiben, Dach nach Bezeichner und Vor Index-Klammer beachten!)

Moderiert von user profile iconKlabautermann: Delphi-Tags hinzugefügt.


Net-Spider - Do 16.09.04 13:42

dazu habe ich auch gleich eine frage, ich möchte auch einzelne bytes lesen, ein puffer nützt mir nicht viel, weil ich 4bytes lese und aus den ergebnissen mir die nächste lesestelle errechne.

was wäre dafür die beste möglichkeit, es handelt sich dabei um ein paar dateien mit 3mb-5mb pro datei bei rund 4*10000 lesevorgängen oder anders gesagt will ich ca. alle 400 bytes 4 bytes lesen


jasocul - Do 16.09.04 13:53

Den folgenden Tip habe ich nicht getestet, aber das müsste eigentlich funktionieren:
Definiere die Datei-Variable einfach als typisierte Datei (File of Byte). Besser vielleicht File of Array[0..3] of Byte, da Du ja immer 4 Byte einliest. Das hängt aber davon ab, ob die Daten-"länge", die dazwischen stehen, auch immer durch 4 teilbar sind.
Danach benutzt Du die Seek-Prozedur. Damit kannst Du dann direkt eine Stelle in der Datei "anspringen".


Net-Spider - Do 16.09.04 14:00

schonmal danke für die schnelle antwort, aber ich weiß immer nicht WIE ich die datei öffnen soll, mir geistert da immer openfile, fileopen, appendfile im kopf rum aber das scheinen nicht die richtigen zu sein, zumindest laut delphi hilfe


jasocul - Do 16.09.04 14:29


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
procedure Dateilesen;
var
  f : file of byte;
  b4 : Array [0..3of byte;
  cnt : Integer;
  Position : LongInt;
begin
  Assign(f, 'C:\TEST.TXT');
  Reset(f);
  for cnt := 0 to 3 do read(f, b4[cnt]);
  //Position := BerechnePosAus4Byte(b4);
  seek(f, Position);
  // Hier dann weitere Daten lesen
  Close(f);
end;


Falls noch Fragen sind, denn sach bescheid.


grayfox - Do 16.09.04 14:43

und ich dachte immer, dass man mit 'AssignFile' das file öffnet & mit 'CloseFile' wieder schliesst ;)


jasocul - Do 16.09.04 14:47

Ich meine, dass das für Text-Files vorgesehen ist. Oder war es um Verwechselungen mit dem Assign-Befehl zu vermeiden? :gruebel:


Net-Spider - Do 16.09.04 15:01

ah, danke, das hilft mir weiter, nun kann ich die mp3dateien weiter einlesen (nicht ID3-Tags)


BenBE - Do 16.09.04 18:31

Stimmt so nicht:
AssignFile initialisiert den MBC, der von Delphi verwendet wird, um die Datei anzusprechen.
Direkt geöffnet wird die Datei erst durch Reset, Append und Rewrite.


jasocul - Do 16.09.04 19:01

@BenBE
Da habe ich wohl zu hastig gelesen. Du hast natürlich Recht. Mit AssignFile werden die Dateien natürlich nicht geöffnet.


Heiko - Do 28.04.05 09:45

Habe ich euch richtig verstanden, dass TFileStream schneller ist als die Variante von jasocoul mit AssignFile, ...?

Ich habe es erstmal mit diese Variante hier probiert:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
procedure TForm1.Button1Click(Sender: TObject);
var DateiFile: TextFile;
    ReadedByte: Byte;
    i, j: Cardinal;
    SignCounter: array[0..255of Cardinal;
begin
 if GetFileSizeEx('WMEncoder.exe')<=4294967295 then
  begin
   for i:=0 to 255 do
    begin
     SignCounter[i]:=0;
    end;
   AssignFile(DateiFile, 'WMEncoder.exe');
   Reset(DateiFile);
   while not eof(DateiFile) do
    begin
     Read(DateiFile, ReadedByte);
     inc(SignCounter[ReadedByte])
    end;
   CloseFile(DateiFile);
   for i:=0 to 255 do
    begin
     ShowMessage(IntToStr(i)+': '+IntToStr(SignCounter[i]));
    end;
  end
 else
  begin
   Application.MessageBox('Die ausgewählte Datei ist für diese Version zu groß!''Datei zu Groß'16)
  end;
end;


Diese Variante ist aber wirklich sehr langsam. Da ich dann dieses Thema hier mir durchgelesen hatte, habe ich mal folgende Variante probiert:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
procedure TForm1.Button1Click(Sender: TObject);
var DateiFile: TextFile;
    ReadedString: Strings[1024]//Fehlermeldung von Delphi, da der Wert in [] nicht größer als 255 sein darf
    ReadedByte: Byte;
    i, j: Cardinal;
    SignCounter: array[0..255of Cardinal;
begin
 if GetFileSizeEx('WMEncoder.exe')<=4294967295 then
  begin
   for i:=0 to 255 do
    begin
     SignCounter[i]:=0;
    end;
   AssignFile(DateiFile, 'WMEncoder.exe');
   Reset(DateiFile);
   while not eof(DateiFile) do
    begin
     Read(DateiFile, ReadedString);
     for j:=1 to length(ReadedString)+1 do
      begin
       ReadedByte:=Ord(ReadedString[j]);
       inc(SignCounter[ReadedByte])
      end
    end;
   CloseFile(DateiFile);
   for i:=0 to 255 do
    begin
     ShowMessage(IntToStr(i)+': '+IntToStr(SignCounter[i]));
    end;
  end
 else
  begin
   Application.MessageBox('Die ausgewählte Datei ist für diese Version zu groß!''Datei zu Groß'16)
  end;
end;


Ich bekomme aber immer Fehlermeldungen. Wie mancht ihr es dann (also k*512 Bytes auslesen)?
Und warum sollte man gerade k*512 Bytes auslesen?


BenBE - Do 28.04.05 22:14

512 Bytes ist genau ein logischer Sektor auf der Festplatte (ein physikalischer Sektor sind 520 IIRC), aber Windows liest generell ganze Sektoren von der Festplatte. Wenn Du also immer 512 Bytes (oder Vielfache) liest, bleibst Du immer genau am Anfang eines Sektors und Windows muss die Lesezugriffe nicht aufspalten, d.h. weniger Lesezugriffe --> Schnellerer Zugriff.

@Puffer: String[1024] kann nicht gehen, da es einen ShortString definieren würde und diese sind auf 256 Bytes begrenzt.

Definiere statt dessen einen ReadedString: Array [0..1023of Char, Basisindex ist dann Index 0.

BTW: Es müsste eigentlic ReadString heißen, da Read in allen drei Formen gleich bleibt.


Heiko - Fr 29.04.05 19:59

Wie soll ich das realisieren das er gleich 512 Bytes ausliest?

Den Quelltext von jasocoul versteh ich irgendwie nicht ganz. Was bedeutet zum Beispiel seek? Und warum ist seine Variante schneller? Er liest die Bytes auch ja einzel aus. Oder sollte ich es lieber mit TFileStream versuchen?


BenBE - Fr 29.04.05 20:15

user profile iconHeiko hat folgendes geschrieben:
Wie soll ich das realisieren das er gleich 512 Bytes ausliest?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure ReadBytesFast;
var
    Puffer: array[0..1023of byte;
    F: File;
begin
    {... Datei öffnen und so ... }
    BlockRead(F, Puffer[0], 1024);
    {... Datei schließen und so ... }
end;


user profile iconHeiko hat folgendes geschrieben:
Den Quelltext von jasocoul versteh ich irgendwie nicht ganz.
Ist auch eine schlechte Angewohnheit in meinen Augen, mit den TP-Befehlen für IO unter Delphi zu arbeiten.

Er hat nur ähnlich wie ich jetzt oben, die Open- und Close-Dinge für Dateizugriff weggelassen. Ansonsten ist seine Variante mit deiner letzten ähnlich.

user profile iconHeiko hat folgendes geschrieben:
Was bedeutet zum Beispiel seek?

Die DOH ist dein bester Freund: Seek (von engl. Suchen) springt in einer Datei an eine andere Position.

user profile iconHeiko hat folgendes geschrieben:
Und warum ist seine Variante schneller? Er liest die Bytes auch ja einzel aus.

Ist se nicht, hab ich in meinem vorigen Posting erklärt ... er liest ein Byte pro Dateizugriff und hat dadurch sehr viel Verwaltungsaufwand seitens des OS, während das Lesen ganzer Blöcke wesentlich schneller geht.

user profile iconHeiko hat folgendes geschrieben:
Oder sollte ich es lieber mit TFileStream versuchen?

Jip. das ist von der Logik und der Implementierung einfacher (und zeitgemäßer). Geht etwa so hier:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure ReadBytesFastByFileStream;
var
    Puffer: array[0..1023of byte;
    FS: TFileStream;
begin
    FS := TFileStream.Create('C:\Datei.dat', fmOpenRead or fmShareDenyWrite);
    try
        FS.ReadBuffer(Puffer[0], 1024);
    finally
        FreeAndNil(FS);
    end;
end;


Mit FS.Position kannst Du die Position ändern (oder FS.Seek direkt aufrufen)


Heiko - Fr 29.04.05 20:20

Normalerweise arbeite ich ja mit TFileStream. Es gab nur einen Grund warum ich ihn hier nicht verwendet hatte. Gibt es dort z.B. den Befehl eof? Und ich habe es auch nicht verwendet, da ich sonst immer nur bekannte Formate auslese, und nicht unbekannte.


BenBE - Fr 29.04.05 20:24

Direkt als Abfrage gibt es in Streams den EOF-Befehl nicht, aber Du kannst Dir mit FS.Position >= FS.Size den EOF-Befehl ganz leicht nachbauen.


Heiko - Fr 29.04.05 20:26

Und ist es egal ob ich FileStream.Free nehme oder FreeAndNil(FileStream)?

Und woran erkenne ich wieviel Bytes im letzten Sektor für mich wirklich von Bedeutung sind? Denn es kann ja sein, dass ich nur 10 Bytes vom letzten Sektor benötige. Oder muss ich das mit modulo selber herausfinden?


BenBE - Fr 29.04.05 20:32

user profile iconHeiko hat folgendes geschrieben:
Und ist es egal ob ich FileStream.Free nehme oder FreeAndNil(FileStream)?

Eigentlich ja, es gibt nur einen bedeutenden Vorteil von FreeAndNil: Die Referenz auf das Objekt wird auch gleich gelöscht, womit der Aufruf von Assigned korrekt False ergibt. Das Manuelle auf Nil setzen entfällt somit.

user profile iconHeiko hat folgendes geschrieben:
Und woran erkenne ich wieviel Bytes im letzten Sektor für mich wirklich von Bedeutung sind? Denn es kann ja sein, dass ich nur 10 Bytes vom letzten Sektor benötige. Oder muss ich das mit modulo selber herausfinden?
Nein, brauchst Du nicht.

Wenn Du mit FS.Read anstatt mit FS.ReadBuffer arbeitest, werden nur soviele Bytes zurückgegeben, wie tatsächlich vorhanden sind. Dass nur 10 Bytes gelesen wurden, gibt die Funktion dann als Result zurück.

Delphi-Quelltext
1:
ReadSize := FS.Read(Puffer[0], 512);                    

Der Rest (ab dem 11. Byte) ist dann aber undefiniert.


Heiko - Sa 30.04.05 10:20

thx,

das Auslesen braucht jetzt mit FileStream nur halbe Sekunde, anstatt 10 Minuten mit der alten Variante :shock: .


Heiko - Sa 30.04.05 10:39

Für 523 MB braucht meiner nur 15 Sekunden :shock: !! Ich möcte nicht wissen, wie lange der mit meiner ertsne Variante gebraucht hätte. Eine Woche? Spielt jetzt aber keine Rolle mehr.

Aso es scheint egal zu sein, ob man nun 1024 oder 2048 mit einmal ausliest. Man macht kein sichtbaren Gewinn mehr. Es lohnt sich aber statt 512 1024 zu nehmen.


Heiko - Mo 02.05.05 17:08

Also irgendwie liest der nicht das ein was ich will :gruebel: :

Quelltext
1:
2:
3:
NY@ÞÏk|ž+ñ–‚+£fûËœh—èR@#: ÏQ™uÊ
­ßåúíhþ¯ÓÜ/aÁ©éjíá&M±^êØio¶ˆ*¾Í»[é¾%(ÙJ­eDؘað?ÏÂ’$œ‰—
{ßErÝGŠëÀ…"gØ4lö·îP8Ë´I¿p‹l=œ"¬t²“1lMQ[oçb÷ßÁz‘¨È…ñ'‡¡¼}? 5ç 9âLnýwÒè>Ù”„¯


Das sollte eigentlich alle 256 ASCII-Code-Zeichen sein. Ich glaube nicht, dass die das sind.

Hier der Quelltext:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
procedure Kompression(const DateiFileStream: TFileStream; FileStr: String);
var i: Cardinal;
    SignCounter: array[0..255of Cardinal;
    ReadBuffer: array[0..511of Byte;
    ReadSize: Word;
    TakeSign: array of Byte;
//    DateiFile: File of Byte;
    KompFileStream: TfileStream;
begin
 for i:=0 to 255 do
  begin
   SignCounter[i]:=0;
  end;
 while DateiFileStream.Position<DateiFileStream.Size do
  begin
   ReadSize:=DateiFileStream.Read(ReadBuffer[0], 512);
   for i:=0 to ReadSize-1 do
    begin
     inc(SignCounter[ReadBuffer[i]])
    end
  end;
// AssignFile(DateiFile, Copy(FileStr, 1, length(FileStr)-length(ExtractFileExt(FileStr)))+'.pck');
 KompFileStream:=TFileStream.Create(Copy(FileStr, 1, length(FileStr)-length(ExtractFileExt(FileStr)))+'.pck', fmCreate or fmOpenWrite or fmShareDenyWrite);
// Rewrite(DateiFile);
 for i:=0 to 255 do
  begin
   if SignCounter[i]>0 then
    begin
     SetLength(TakeSign, High(TakeSign)+2);
     TakeSign[High(TakeSign)]:=SignCounter[i];
     KompFileStream.WriteBuffer(SignCounter[i], 1)
//     Write(DateiFile, SignCounter[i]);
    end
  end;
// CloseFile(DateiFile);
 FreeAndNil(KompFileStream);
end;

procedure TForm1.Button1Click(Sender: TObject);
var DateiFileStream: TFileStream;
    OpenDialog: TOpenDialog;
begin
 OpenDialog:=TOpenDialog.Create(Self);
 if OpenDialog.Execute then
  begin
   DateiFileStream:=TFileStream.Create(OpenDialog.FileName, fmOpenRead or fmShareDenyWrite);
   if DateiFileStream.Size<=4294967295 then
    begin
     Kompression(DateiFileStream, OpenDialog.FileName);
    end
   else
    begin
     Application.MessageBox('Die ausgewählte Datei ist für diese Version zu groß!''Datei zu Groß'16)
    end;
   FreeAndNil(DateiFileStream);
  end
end;


Heiko - Di 03.05.05 10:56

Ok ich habe den Fehler gefunden. Hier die Lösung [http://www.delphi-forum.de/viewtopic.php?t=41298&start=0&postdays=0&postorder=asc&highlight=].


KidPaddle - Di 03.05.05 12:57

Ich würde den Code wie folgt ändern. Der Code könnte noch optimiert und eine verbesserte Fehlerprüfung müßte noch eingebaut werden.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
procedure TForm1.Button1Click(Sender: TObject);
var DateiFile: TextFile;
    ReadedString: array[0..1023of Byte; <--- Datentyp geaendert
    iBytesRead : Integer;
    ReadedByte: Byte; <---- Neu
    i, j: Cardinal;
    SignCounter: array[0..255of Cardinal;
begin
 if GetFileSizeEx('WMEncoder.exe')<=4294967295 then
  begin
   for i:=0 to 255 do
    begin
     SignCounter[i]:=0;
    end;
   AssignFile(DateiFile, 'WMEncoder.exe');
   Reset(DateiFile);
   while not eof(DateiFile) do
    begin
     iBytesRead := Read(DateiFile, ReadedString); // Wieviele Zeichen wurden gelesen
     if (iBytesRead > 0then // <-- Es wurden Daten gelesen
      begin
       for j:= 0 to (iBytesRead - 1do
        begin
         ReadedByte:=ReadedString[j];
         inc(SignCounter[ReadedByte])
        end
      end;
    end;
   CloseFile(DateiFile);
   for i:=0 to 255 do
    begin
     ShowMessage(IntToStr(i)+': '+IntToStr(SignCounter[i]));
    end;
  end
 else
  begin
   Application.MessageBox('Die ausgewählte Datei ist für diese Version zu groß!''Datei zu Groß'16)
  end;
end;


Gruß
KidPaddle