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..0] of 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
Klabautermann: 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..3] of byte; cnt : Integer; Position : LongInt; begin Assign(f, 'C:\TEST.TXT'); Reset(f); for cnt := 0 to 3 do read(f, b4[cnt]); seek(f, Position); 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..255] of 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]; ReadedByte: Byte; i, j: Cardinal; SignCounter: array[0..255] of 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..1023] of 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
Heiko 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..1023] of byte; F: File; begin BlockRead(F, Puffer[0], 1024); end; |
Heiko 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.
Heiko 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.
Heiko 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.
Heiko 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..1023] of 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
Heiko 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.
Heiko 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¶ˆ*¾Í»[é¾%(ÙJeDؘ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..255] of Cardinal; ReadBuffer: array[0..511] of Byte; ReadSize: Word; TakeSign: array 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; KompFileStream:=TFileStream.Create(Copy(FileStr, 1, length(FileStr)-length(ExtractFileExt(FileStr)))+'.pck', fmCreate or fmOpenWrite or fmShareDenyWrite); 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) end end; 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; |
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..1023] of Byte; <--- Datentyp geaendert iBytesRead : Integer; ReadedByte: Byte; <---- Neu i, j: Cardinal; SignCounter: array[0..255] of 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); if (iBytesRead > 0) then begin for j:= 0 to (iBytesRead - 1) do 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
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!