Tutorial
Optimierungen
von ThePuppetMaster | Seite 3 von 9 |
Geschwindigkeitsoptimierung
Hier gibt es so viele Variationen, dass es schwer ist, wenn nicht sogar unmöglich, all diese Möglichkeiten aufzuführen.
Threads hinzufügen, SSE und MMX Optimierung, Konstanten Optimierung, AndAlso & OrElse verwenden, ...
Um Threading zu Integrieren und zu Optimieren empfehle ich folgende Tutorials:
Generell muss ich leider sagen, das auf OOP so weit wie möglich verzichtet werden sollte, wenn es darum geht ein Programm so effektiv und schnell wie möglich zu schreiben.
OOP ist eine Abstraktion und damit eine Verlangsamung. Natürlich kann man hier auch die Tatsache einwerfen, man möge ja auf ASM umstellen, wenn man unbedingt schnelleren Code haben möchte.
Diesem Einwand ist an sich nichts hinzu zu fügen, wäre da nicht die Tatsache, dass hier die Basis FreeBASIC bildet. Es ist nun mal ein FB Tutorial und kein ASM Tutorial. Daher versuche ich die besten Optimierungen auf FB zu beziehen, und nicht auf die weltweit bestmöglichen Varianten.
Und für die nimmer satten Kritiker kann ich nur "Optimizing subroutines in assembly language" von "Agner Fog", das Doom9-Forum oder die Lektüren von "Ted Cochran" empfehlen.
Optimierung von sich wiederholenden Konstanten, die nicht zur Entwicklungszeit festgelegt werden können
Solch eine Konstante kann z.B. das CRLF (Zeilenumbruch) sein.
Ein CRLF ist die ASCII Zeichenfolge 13 und 10
Print Chr(13, 10)
Wird diese, z.B. beim Erstellen von HTML-Text häufig angewandt, braucht der Computer etwas Zeit um diese Zeichenfolge zu erzeugen.
Wie das Ganze in FreeBasic gemacht wird, kann man hier sehen: libfb_str_chr.c
Einfach erklärt geht das so:
Function XChr(V_Chr as UByte, ...) String 'Wir nennen die Funktion hier XChr, weil Chr schon existiert. Die Parameterangabe '...' definieren eine unbekannte Anzahl Parameter
Dim T as String 'Temporäre String Variable erzeugen
Dim TX as UInteger 'Pointer auf das nächste Datenbyte im String
Dim TArg as Any Ptr = Va_First() 'Ersten Parameter Pointer des Funktionskopfes einholen.
Dim TLen as UInteger = 10 'Länge des Temporären Strings (Am Anfang mit 10 Bytes)
T = Space(TLen) 'Der Temporäre String wird mit 10 Byte vorvergrössert.
T[TX] = V_Chr 'Erstes ASCII Zeichen an erste Stelle in String schreiben.
Do Until TArg = 0 'Alle Parameter durchgehen, bis TArg = 0 ist.
TX += 1 'Nächste Zeichenposition festlegen
If TX >= TLen Then 'Wenn String Speicher nicht mehr ausreicht, ...
TLen += 10 '... Dann diesen um 10 Bytes vergrößern ...
T += Space(10) '... Und erzeugen.
End If
T[TX] = va_arg(arg, UByte) 'Nächstes Zeichen aus Funktionsparameter Liste in String schreiben
Arg = va_next(arg, UByte) 'Nächsten Funktions Parameter einholen.
Loop
Return Left(T, TLen) 'Überschuss abschneiden und erzeugte Zeichenliste zurückgeben.
End Function
Wie man im Quellcode sieht, wurde dieser schon leicht optimiert. Nämlich mit einer Vergrößerung des String-Speichers. Dieses wirkt sich positiv auf größere ASCII Parameterlisten aus.
Aber dazu mehr im nächsten Abschnitt.
Zur Erklärung von 'va_first()', 'va_arg()' und 'va_next()' kann folgende Seite herangezogen werden: Freebasic.net Wiki
Um nun solche Konstanten Werte besser im Programm zu händeln ist es sinnvoll (vor allem bei Konstanten Numerischen Werten), diese als #DEFINE via Preprozessor zu inkludieren.
#Define 123
Problem bei dieser Methode ist, das dies nur dann sinnvoll ist, wenn es sich um Konstante Daten handelt die nicht erst erzeugt werden müssen, oder vom Compiler missverstanden werden könnten.
Erst erzeugt werden muss hier z.B. die Zeichenkette Chr(13, 10), da man sie nicht als Klartext schreiben kann.
Man auch leider auch nicht zeilenübergreifende Konstante Werte angeben:
#Define "
"
... was natürlich ungemein praktisch wäre :)
Eine Alternative ist das Erzeugen einer konstanten Variablen, und deren Initialisierung
Dim Shared FBCRLF as String
FBCRLF = Chr(13, 10)
Leicht Optimiert:
Dim Shared FBCRLF as String : FBCRLF = Chr(13, 10)
Eine weitere Variante wäre
Dim Shared FBCRLF as ZString * 2 : FBCRLF[0] = 13: FBCRLF[1] = 10
Dies ist etwas trickreicher, speicherplatz-schonender, und schneller in der Verarbeitung. Nachteilig ist jedoch, das auf diese Art nur Zeichen gespeichert werden können, die > 0 sind. Siehe hierzu: Referenz - ZSTRING.
Der "Trick" liegt hier darin, dass eine feste Zeichenbreite vordefiniert wird. 2 Bytes + 1 Byte End-Marker. Anschließend wird durch direktem Speicherzugriff [] auf das jeweilige Byte zugegriffen und der Wert 13 sowie 10 hinein geschrieben.
Dadurch umgeht man die relativ aufwendige Variante der Chr-Funktion
Eine weitere Optimierung dieses Verfahrens wäre die Reduktion der benötigten Programmzeilen.
Dieser Code ...
Dim Shared FBCRLF as ZString * 2 : FBCRLF[0] = 13: FBCRLF[1] = 10
... würde aufgeschlüsselt, so ...
Dim Shared FBCRLF as ZString * 2
FBCRLF[0] = 13
FBCRLF[1] = 10
... aussehen.
Hier ließe sich das Schreiben der Daten mit einem Weiterem Trick optimieren.
Dim Shared FBCRLF as ZString * 2
*Cast(UShort Ptr, @FBCRLF[0]) = &H0A0D
Dies mag auf den ersten Blick sehr aufwendig und Zeitraubender aussehen. Nun .. das stimmt nur zu einem gewissen teil. Der zeitraubende Faktor ist das Schreiben dieses Codes.
In Wahrheit ist diese Funktion schneller als die vorherige.
Der Grund liegt in dem Trick, dem Compiler mitzuteilen, das die Pointeradresse des ersten Zeichens aus dem ZString (@...[0]) kein UByte darstellt, sondern ein UShort.
Das heißt, wir können jetzt statt eines Bytes ein ganzes Short (2 Byte) auf einmal kopieren und sparen damit eine ASM Instruktionen.
ASM der ersten Variante (fbc <name>.bas -r):
'...
.Lt_0001:
mov byte ptr [FBCRLF], 13
mov byte ptr [FBCRLF+1], 10
.Lt_0002:
'...
und der asm der 2. variante:
'...
.Lt_0001:
mov word ptr [FBCRLF], 2573
.Lt_0002:
'...
Wie gut zu erkennen ist, wurden eine 2-Byte-Instruktion durch eine Word (UShort) Instruktion ersetzt.
Das mag jetzt total übertrieben klingen, derartige Optimierungen in einen Quellcode einzubauen (welcher gerade mal eine Zeile spart), aber man muss dies immer auf das gesamte Anwendungsprozedur sehen.
Werden solche Optimierungen im gesamten Programm verwendet, kann sich die Ausführungsgeschwindigkeit durchaus erhöhen. Auch wenn es auf dem ersten Blick nicht so erscheint.
Jetzt stellt sich noch die Frage, ob es sinnvoller ist, 3 Byte Speicherplatz im Betriebssystem zu belegen, inklusive dem Verwaltungsaufwand den das Betriebssystem handeln muss, im Vergleich zur Integrierung eines #Makros / #Define wie:
#Define FBCRLF Chr(13, 10)
Nun ... Interessanterweise ändert sich im ASM Kompilat nur recht wenig. Nur die Art des handlings ändert sich.
Ob man letztendlich Chr(13, 10) direkt in das Const schreibt, oder aber ein #Define nutzt, ist ASM seitig so minimal, das es egal wird, für was man sich entscheidet.
Aber, dieses Verfahren verdeutlicht, dass man durch kleine Untersuchungen des ASM Outputs auch selbst auf Optimierungen schließen kann, welche später in das Programm einfliegen können und somit helfen, den Code effektiver zu gestalten.
Array Vordimensionierung
Bei der Array Vordimensionierung handelt es sich (wie im letzten Beispiel zu sehen) um eine übermäßige (eigentlich unnötige) Vergrößerung des Speichers. "Eigentlich Unnötig" ist hier subjektiv zu sehen, da in dem vorherigem Beispiel nicht ersichtlich ist, wie viele Zeichen erzeugt werden sollen (Wenn zuvor nicht die Liste einmal komplett durchlaufen wurde).
Eine Vorvergrößerung hat in einigen Anwendungsbereichen durchaus einen gravierenden Vorteil. Ein gutes Beispiel lässt sich mit dem Auslesen des gesamten Festplatteninhalts aufzeigen.
Dim TTime as Double 'Für Zeitmessung
Dim TFolderData() as String 'Ein Dynamisches Array anlegen.
Dim TFolderCount as UInteger 'Eine Variable welche die aktuelle Anzahl an Einträge im Array speichert
Dim TFolderDeep as UInteger 'Hier findet sich die aktuelle Größe des Array's
Dim TFolderX as UInteger 'Ein Zähler (X) der auf einen Eintrag im Array zeigt. (der gerade zu untersuchende Ordner)
TFolderCount = 1 'Wir setzen die Anzahl Einträge auf 1
TFolderDeep = TFolderCount 'und auch die Größe
Redim TFolderData(TFolderCount) as String 'und redimensionieren diese im Array
#IF defined(__fb_linux__) 'Anschließend speichern wir unser Basisverzeichniss dort ab, von dem aus wir Durchsuchen möchten
TFolderData(TFolderCount) = "/usr/share/" 'Linux
#ELSEIF defined(__fb_win32__)
TFolderData(TFolderCount) = "C:\Windows\" 'Windows
#ENDIF
Dim TAttrib as Integer 'Eine Variable welche die Attribute eines Eintrags von Dir speichert
Dim TName as String 'Der Name eines Eintrags von Dir
TTime = Timer() 'Zeitmessung Starten
Do
TFolderX += 1 'Wir erhöhen die Zählervariable um 1. (Definiert das Basisverzeichniss (bzw. die Adresse) im Array das als nächstes durchsucht werden soll)
If TFolderX > TFolderCount Then Exit Do 'Ist die Adresse grösser als alle bekannten Einträge sind wir am Ende.
TName = Dir(TFolderData(TFolderX) & "*", -1, @TAttrib) 'Den Ersten Eintrag im Verzeichnis einholen
Do Until TName = "" 'Diese Schleife solange laufen lassen, bis keine Einträge mehr aufgelistet werden.
If (TName <> ".") and (TName <> "..") Then 'Ist der Eintrag weder ein . noch ein .. dann
If (TAttrib and &H10) <> 0 Then 'prüfen ob der Eintrag ein Verzeichnis ist, und wenn ja dann
TFolderCount += 1 'Anzahl Einträge um +1 erhöhen.
If TFolderCount > TFolderDeep Then 'Ist die Anzahl der Einträge Grösser als die Derzeitige Kapazität im Array, dann
TFolderDeep += 1000 'Arraygröße um 1000 erhöhen
Redim Preserve TFolderData(TFolderDeep) as String 'Und Array neu dimensionieren
End If
#IF defined(__fb_linux__)
TFolderData(TFolderCount) = TFolderData(TFolderX) & TName & "/" 'Anschließend den Aktuell zu durchsuchenden Pfad + dem neuem Namen in das Array schreiben
#ELSEIF defined(__fb_win32__)
TFolderData(TFolderCount) = TFolderData(TFolderX) & TName & "\"
#ENDIF
End If
End If
TName = Dir("", -1, @TAttrib) 'Nächsten Eintrag erfragen
Loop
Loop
TTime = Timer() - TTime 'Zeitmessung stoppen
Print "TFolderCount:"; TFolderCount
Print "TFolderDeep:"; TFolderDeep
Print "TTime:"; TTime & " sek."
Dieses Programm liest die gesamte Verzeichnisstruktur von "C:\Windows\" (win) bzw. "/usr/share/" (Linux) ein.
Das benötigt natürlich einige Zeit was außerordentlich hilfreich ist, um eine handfeste Aussage über den Vorteil von Vordimensioniertem Speicher zu Treffen.
Warum Vordimensionierung Geschwindigkeitsvorteile hat, kann man gut in diesem Tutorial nachlesen: http://www.freebasic-portal.de/ -> Tutorials -> Datenstrukturen -> Baumstrukturen in FreeBASIC -> Seite: 2
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|