Dateiinhalt dynamisch erfassen und speichern

Für allgemeine Fragen zur Programmierung mit PureBasic.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8838
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von NicTheQuick »

Da hätte ich auch was hübsches für dich. Wenn du etwas nicht verstehst, frag einfach.

Der Hintergrund ist eigentlich nur, dass man die Länge des Strings am besten von vornerein festlegt und ihn nicht immer wieder verlängert.

Code: Alles auswählen

Procedure.s FileToHex(file.s)
	Protected fileId.i
	Protected fileSize.i
	Protected result.s, *pResult
	
	fileId = ReadFile(#PB_Any, file)
	If (Not fileId)
		ProcedureReturn ""
	EndIf
	
	fileSize = Lof(fileId)
	
	result = Space(fileSize * 2)
	*pResult = @result
	
	While Not Eof(fileId)
		PokeS(*pResult, RSet(Hex(ReadAsciiCharacter(fileId)), 2, "0"), 2)
		*pResult + 2
	Wend
	
	CloseFile(fileId)
	
	ProcedureReturn result
EndProcedure

Debug FileToHex("/home/nicolas/game1")
Achja, so dauert es bei mir dann mit Debugger 492 ms für knapp ein Megabyte. Ohne Debugger dauert es 193 ms. Aber man kann sicherlich noch was optimieren.
Benutzeravatar
Piwo
Beiträge: 27
Registriert: 05.08.2012 22:40

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von Piwo »

Klasse, danke Dir :D
der code funktioniert sehr gut vielen dank :)
Habe nur eine frage zu diesem code-teil hier:

Code: Alles auswählen

result = Space(fileSize * 2)
*pResult = @result
Zunächst mal wieso müssen doppelt so viele leerzeichen reingeschrieben werden als die datei groß ist?
Desweiteren konnte ich nicht rausfinden was der @-operator verusacht, ist es wie bei C++ eine referenz?

Ich finde das nur wirklich unglaublich dass die bloße voreinstellung der string-länge die sequenz auf einen winzigen bruchteil der dauer verkürzt. Gibt es da noch einen weiteren grund der das beschleunigt oder ist es wirklich einzig und allein die vordefinition der größe?
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8838
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von NicTheQuick »

Piwo hat geschrieben:Zunächst mal wieso müssen doppelt so viele leerzeichen reingeschrieben werden als die datei groß ist?
Der Hex-String wird natürlich doppelt so lange, weil du pro Byte in der Datei zwei Hexzeichen brauchst.
Piwo hat geschrieben:Desweiteren konnte ich nicht rausfinden was der @-operator verusacht, ist es wie bei C++ eine referenz?
Genau. Der @-Operator gibt dir unter anderem den Pointer zum ersten Zeichen in einem String zurück. Er kann dir aber genauso gut den Pointer zu jeder anderen Variablen oder Funktion zurückgeben. Dann gibt es noch den ?-Operator, der dir einen Pointer zu einer Sprungmarke zurück gibt.

In meinem Code nehme ich also den Pointer zu meinem String 'result', der schon so viele Zeichen beinhaltet wie ich benötige. Dann überschreibe ich diese Zeichen nach und nach mit 'PokeS' bis der anfangs aus Leerzeichen bestehende String komplett durch die Hex-Werte ersetzt wurde.
Der Code funktioniert momentan übrigens nur, wenn du ihn ohne Unicode kompilierst. Möchtest du Uni-Code verwenden, solltest du statt '*pResult + 2' einfach '*pResult + 2 * SizeOf(Character)' schreiben, weil dann ein einziges Zeichen im String zwei Byte belegt. Und 'SizeOf(Character)' gibt dir die passende Zeichenlänge aus und passt sich an die entsprechende Compileroption an.

In deinem vorherigen Code hast du in jedem Schleifendurchlauf den String um ein Zeichen erweitert. Dieses Vorgehen ist deshalb sehr langsam, weil folgendes passiert:
Zuerst ist dein String ja leer, aber trotzdem belegt er im Speicher schon mal ein paar Bytes, ich glaube bei PB waren das 16 Bytes. Das heißt, wenn du jetzt 15 Zeichen (plus anschließendes Nullbyte) eingelesen hast, sind diese 16 Bytes belegt und es müssen weitere 16 Bytes dahinter alloziert werden, damit noch mehr Zeichen rein passen. Das kann eine Weile so gut gehen bis nach hinten kein Platz mehr frei ist, weil der zum Beispiel für irgendwelche anderen Variablen benutzt wird. Dann muss ein komplett anderer Speicherplatz gesucht werden, in den der neue längere String rein passt. Dann wird dieser alloziert, der alte String rein kopiert, das neue Zeichen hinten dran gehängt und der alte Speicherplatz wieder frei gegeben. Und genau dieser letzte Schritt ist das, was bei deiner Version so lange dauert.
Immer, wenn der String nicht mehr in seinen alten Speicherbereich passt, muss er wo anders hin kopiert werden. Und wenn das regelmäßig passiert, dann ist das rein laufzeittechnisch gesehen eine quadratische Laufzeit.

Ich denke du kannst noch mehr Geschwindigkeit rausholen, wenn du den Hex-String selbst berechnest und rein schreibst. Ich werde das gleich mal dazu basteln.
Benutzeravatar
mk-soft
Beiträge: 3902
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von mk-soft »

Hier noch ein mal Verbesserung um den Faktor 6

Code: Alles auswählen

Procedure.s FileToHex(file.s)
   Protected fileId.i
   Protected fileSize.i
   Protected result.s, *pResult.byte, char, byte
   
   fileId = ReadFile(#PB_Any, file)
   If (Not fileId)
      ProcedureReturn ""
   EndIf
   
   fileSize = Lof(fileId)
   Debug filesize
   result = Space(fileSize * 2)
   *pResult = @result
   
   While Not Eof(fileId)
     char = ReadAsciiCharacter(fileId)
     byte = char >> 4 & $f + '0'
     If byte > '9'
       byte + ('A' - $3A )
     EndIf
     *pResult\b = byte
     *pResult + 1
     byte = char & $f + '0'
     If byte > '9'
       byte + ('A' - $3A )
     EndIf
     *pResult\b = byte
     *pResult + 1
   Wend
   
   CloseFile(fileId)
   
   ProcedureReturn result
EndProcedure

file.s = OpenFileRequester("Datei", "", "", 0)
start = ElapsedMilliseconds()
r1.s = FileToHex(file)
ende = ElapsedMilliseconds()
MessageRequester("test", "Zeit " + Str(ende-start) + "ms")
Geht also noch schneller :allright:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8838
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von NicTheQuick »

mk-soft hat geschrieben:Geht also noch schneller :allright:
Schon lustig wie stark der Code meinem ähnelt. Ich hab es nämlich genauso gemacht wie du, bloß hab ich mich nicht verrechnet. :P Vielleicht findest du deinen Fehler ja selbst. :wink:

...

Mist, du hast es ja schon korrigiert. :D
Benutzeravatar
mk-soft
Beiträge: 3902
Registriert: 24.11.2004 13:12
Wohnort: Germany

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von mk-soft »

@NicTheQuick

Hab vergessen 'korrigiert' zu schreiben.
Wie sieht den Dein Code aus? Berechnete Konstanten werden vom Kompiler optimiert ('A' - $3A)

FF :wink:
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8838
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von NicTheQuick »

mk-soft hat geschrieben:Wie sieht den Dein Code aus?
So:

Code: Alles auswählen

Procedure.s FileToHex(file.s)
	Protected fileId.i, fileSize.i, byte.a
	Protected result.s, low.c
	Protected *pResult.Character
	
	fileId = ReadFile(#PB_Any, file)
	If (Not fileId)
		ProcedureReturn ""
	EndIf
	
	fileSize = Lof(fileId)
	
	result = Space(fileSize * 2)
	*pResult = @result
	
	While Not Eof(fileId)
		byte = ReadAsciiCharacter(fileId)
		low = byte & $F + '0'
		byte = byte / 16 + '0'
		If (byte > '9')
			byte + 7	;'A' - '9' - 1 = 7
		EndIf
		If (low > '9')
			low + 7		;'A' - '9' - 1 = 7
		EndIf
		*pResult\c = byte
		*pResult + SizeOf(Character)
		*pResult\c = low
		*pResult + SizeOf(Character)
	Wend
	
	CloseFile(fileId)
	
	ProcedureReturn result
EndProcedure
Aber ich wollte jetzt noch eine Optimierung einbauen, die beide Hexzeichen auf einmal schreibt und nicht nacheinander. Da braucht man im Grunde auch nur zwei Ifs zusätzlich, wie jetzt schon, kann aber beide Zeichen am Ende am Stück rein schreiben.
Benutzeravatar
ts-soft
Beiträge: 22292
Registriert: 08.09.2004 00:57
Computerausstattung: Mainboard: MSI 970A-G43
CPU: AMD FX-6300 Six-Core Processor
GraKa: GeForce GTX 750 Ti, 2 GB
Memory: 16 GB DDR3-1600 - Dual Channel
Wohnort: Berlin

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von ts-soft »

Wenn Ihr die Datei auf einmal in den Speicher ladet, statt Byte-Weise, und dort umwandelt, sollte es nochmal um mind.
dem Faktor 20 schneller werden :wink:
PureBasic 5.73 LTS | SpiderBasic 2.30 | Windows 10 Pro (x64) | Linux Mint 20.1 (x64)
Nutella hat nur sehr wenig Vitamine. Deswegen muss man davon relativ viel essen.
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8838
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von NicTheQuick »

ts-soft hat geschrieben:Wenn Ihr die Datei auf einmal in den Speicher ladet, statt Byte-Weise, und dort umwandelt, sollte es nochmal um mind.
dem Faktor 20 schneller werden :wink:
Soweit ich weiß, ist die File-Library von PB doch schon seit einigen Versionen gepuffert. Deswegen sollte es eigentlich keinen Unterschied machen, aber ausprobieren könnte man es natürlich schon.

Egal, hier meine Version, die mit und ohne Unicode funktioniert und außerdem beide Hexwerte in einem Rutsch speichert.

Code: Alles auswählen

Procedure.s FileToHex(file.s)
	Protected fileId.i, fileSize.i
	Protected result.s, Dim hexa.c(1)
	CompilerIf #PB_Compiler_Unicode
		Protected *pResult.Long
		Protected *value.Long = @hexa(0)
	CompilerElse
		Protected *pResult.Unicode
		Protected *value.Unicode = @hexa(0)
	CompilerEndIf
	
	fileId = ReadFile(#PB_Any, file)
	If (Not fileId)
		ProcedureReturn ""
	EndIf
	
	fileSize = Lof(fileId)
	
	result = Space(fileSize * 2)
	*pResult = @result
	
	While Not Eof(fileId)
		hexa(0) = ReadAsciiCharacter(fileId)
		hexa(1) = hexa(0) & $F + '0'
		hexa(0) = hexa(0) / 16 + '0'
		If (hexa(0) > '9')
			hexa(0) + 7	;'A' - '9' - 1 = 7
		EndIf
		If (hexa(1) > '9')
			hexa(1) + 7		;'A' - '9' - 1 = 7
		EndIf
		CompilerIf #PB_Compiler_Unicode
			*pResult\l = *value\l
			*pResult + 4
		CompilerElse
			*pResult\u = *value\u
			*pResult + 2
		CompilerEndIf
	Wend
	
	CloseFile(fileId)
	
	ProcedureReturn result
EndProcedure

#n = 50

Define time.i = ElapsedMilliseconds(), s.s
For i = 1 To #n
	s.s = FileToHex("/home/nicolas/tmp/Handspirale.gif")
	Debug s
Next
MessageRequester("", Str((ElapsedMilliseconds() - time) / #n))
Bei mir dauert das Umwandeln einer 938427 Bytes großen Datei durchschnittlich bei 50 Versuchen 75 ms.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8838
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 64 GB DDR4-3200
Ubuntu 24.04.2 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken

Re: Dateiinhalt dynamisch erfassen und speichern

Beitrag von NicTheQuick »

Und jetzt noch die Version mit vorherigem kompletten Einlesen in den Speicher.

Code: Alles auswählen

Procedure FileToMemory(file.s, *fileSize.Integer = 0)
	Protected *memory
	Protected fileId.i
	Protected fileSize.i
	
	If (*fileSize)
		*fileSize\i = 0
	EndIf
	
	fileId = ReadFile(#PB_Any, file)
	If (Not fileId)
		ProcedureReturn #False
	EndIf
	
	fileSize = Lof(fileId)
	
	*memory = AllocateMemory(fileSize, #PB_Memory_NoClear)
	If (Not *memory)
		CloseFile(fileId)
		ProcedureReturn #False
	EndIf
	
	If (ReadData(fileId, *memory, fileSize) <> fileSize)
		CloseFile(fileId)
		FreeMemory(*memory)
		ProcedureReturn #False
	EndIf
	
	CloseFile(fileId)
	
	If (*fileSize)
		*fileSize\i = fileSize
	EndIf
	
	ProcedureReturn *memory
EndProcedure

#USE_MEMORY = #True

Procedure.s FileToHex(file.s)
	Protected result.s, Dim hexa.c(1)
	CompilerIf #PB_Compiler_Unicode
		Protected *pResult.Long
		Protected *value.Long = @hexa(0)
	CompilerElse
		Protected *pResult.Unicode
		Protected *value.Unicode = @hexa(0)
	CompilerEndIf
	
	Protected fileSize.i
	CompilerIf #USE_MEMORY
		Protected *memFile, *memWalk.Ascii
		*memFile = FileToMemory(file, @fileSize)
		*memWalk = *memFile
	CompilerElse
		Protected fileId.i
		fileId = ReadFile(#PB_Any, file)
		If (Not fileId)
			ProcedureReturn ""
		EndIf
		fileSize = Lof(fileId)
	CompilerEndIf
	
	result = Space(fileSize * 2)
	*pResult = @result
	
	CompilerIf #USE_MEMORY
	While fileSize
		fileSize - 1
		hexa(0) = *memWalk\a
		*memWalk + 1
	CompilerElse
	While Not Eof(fileId)
		hexa(0) = ReadAsciiCharacter(fileId)
		CompilerEndIf
		
		hexa(1) = hexa(0) & $F + '0'
		hexa(0) = hexa(0) / 16 + '0'
		If (hexa(0) > '9')
			hexa(0) + 7	;'A' - '9' - 1 = 7
		EndIf
		If (hexa(1) > '9')
			hexa(1) + 7		;'A' - '9' - 1 = 7
		EndIf
		CompilerIf #PB_Compiler_Unicode
			*pResult\l = *value\l
			*pResult + 4
		CompilerElse
			*pResult\u = *value\u
			*pResult + 2
		CompilerEndIf
	Wend
	
	CompilerIf #USE_MEMORY
		FreeMemory(*memFile)
	CompilerElse
		CloseFile(fileId)
	CompilerEndIf
	
	ProcedureReturn result
EndProcedure

#n = 50

Define time.i = ElapsedMilliseconds(), s.s
For i = 1 To #n
	s.s = FileToHex("/home/nicolas/tmp/Handspirale.gif")
	Debug s
Next
MessageRequester("", Str((ElapsedMilliseconds() - time) / #n))
Die Konstante '#USE_MEMORY' kann benutzt werden um zwischen beiden Versionen zu schalten. Mit dem Vorladen der Datei in den Speicher braucht die Version bei mir nur noch 47ms statt 75ms. Zwar nicht 20 mal schneller, aber immerhin. :wink:
Antworten