Seite 2 von 3

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 26.03.2013 19:09
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.

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 26.03.2013 21:59
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?

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 11:30
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.

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 12:13
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:

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 12:20
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

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 12:32
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:

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 12:55
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.

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 13:13
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:

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 13:22
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.

Re: Dateiinhalt dynamisch erfassen und speichern

Verfasst: 27.03.2013 13:31
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: