Hallo,
ich überlege gerade, wie man am schnellsten in Abhängigkeit von einer
Variable in einer Funktion verzweigen kann.
Standardmäßig wäre ja:
1
switch(x){
2
case43:break;
3
case78:break;
4
...
5
}
Intern macht der Compiler da ja wohl so etwas wie
1
if(43)...
2
elseif(78)...
3
elseif.....
draus.
Wenn ich jetzt mal davon ausgehe, dass x nur Werte von 0 bis 255
annehmen kann, und ich genug Speicher habe, wäre doch eine Sprungtabelle
eine feine Sache.
Nun könnte man ja ein Array mit Function Pointern verwenden, aber
letztlich will ich ja keine Funktionen aufrufen, die auch noch einen
Hin- und einen Rücksprung nach sich ziehen, sondern nur innerhalb eines
Funktionsabschnitts (ähnlich 'switch') in Unterbereiche verzweigen, dass
aber möglichst schnell.
Macht es eigentlich Geschwindigkeitstechnisch einen Unterschied, ob die
Programmausführung nur zweimal Spring (wie es bei switch der Fall sein
dürfte; Anspringen der case Position und mit break dann zum Ende des
switch Blocks) oder ob ich ein Unterprogramm mit Hin- und Rücksprung +
Stackmanipulation ausgeführt wird (ohne Parameter)?
In Assembler würde man hier warscheinlich computed gotos verweden. Das
ginge dann auch immer mit relativen Sprüngen.
Vielleicht so etwas wie:
> Vielleicht so etwas wie:
Die Chancen stehen nicht schlecht, dass der Compiler genau
so etwas bei einem switch-case implementiert.
Einfach mal ausprobieren und den Assembler Code anschauen.
> Intern macht der Compiler da ja wohl so etwas wie> if (43) ...> else if (78) ...>else if .....>> draus.
Bei wenigen Werten in der Regel ja.
> Wenn ich jetzt mal davon ausgehe, dass x nur Werte von 0 bis 255> annehmen kann, und ich genug Speicher habe, wäre doch eine> Sprungtabelle eine feine Sache.
Deshalb machen Compiler das auch so, sofern die Anzahl an case-Labels so
hoch ist, daß es sich lohnt.
Nun ja, dann scheint der Compiler im Visual Studio (ok kein GCC)
00587CDA mov eax,dword ptr [nCtlColor]
00587CDD mov dword ptr [ebp-0DCh],eax
00587CE3 cmp dword ptr [ebp-0DCh],1
00587CEA je CBaseView::OnCtlColor+0EFh (587D8Fh)
00587CF0 cmp dword ptr [ebp-0DCh],3
00587CF7 je CBaseView::OnCtlColor+67h (587D07h)
00587CF9 cmp dword ptr [ebp-0DCh],6
00587D00 je CBaseView::OnCtlColor+67h (587D07h)
00587D02 jmp CBaseView::OnCtlColor+0F4h (587D94h)
entwerder nicht sehr gut zu sein, oder er benutzt die Sprungtabelle nur
wenn nicht im Debug Modus compilert wird, nur weiß ich da nicht, wie ich
die Stelle im Objectcode finden soll, da ja eine Debugging Informationen
mehr drin stecken.
Oben wird aber mit #1, #3 und #6 verglichen und entsprchend gesprungen
(je) und wenn nichts zutrifft mit jmp in den default-Zweig der
switch-Anweisung.
Das Problem mit Function-Pointern ist, dass da ja Funktionen aufgerufen
werden, die ihren eigenen Scope haben, also ich nicht lokale Variabeln
der aufrufenden Funktion weiter verwenden kann, ohne sie irgendie
weiterzureichen, was, wie auch immer man es macht, in jedem Fall auch
Rechenzeit kostet.
Grüße,
Bernd
Der Compiler von Visual Studio ist im allgemeinen sehr gut, was die
Optimierungen betrifft. Im Debug-Modus wird natürlich nicht optimiert.
Also compilier es im Realease-Modus und mach dir erst Gedanken üder
solche Probleme, wenn das Programm wirklich zu langsam geworden ist. Die
C/C++ Compiler sind heutzutage so gut, dass du als mittelmäßiger
Assemblerprogrammierer mit Sicherheit keinen besseren Code programmieren
kannst. Der wichtigste Ansatzpunkt für Optimierungen ist natürlich immer
noch der Algorithmus.
Bernd wrote:
Das was ... (Gast) bereits sagte.
Plus
> 00587CDA mov eax,dword ptr [nCtlColor]> 00587CDD mov dword ptr [ebp-0DCh],eax> 00587CE3 cmp dword ptr [ebp-0DCh],1> 00587CEA je CBaseView::OnCtlColor+0EFh (587D8Fh)> 00587CF0 cmp dword ptr [ebp-0DCh],3> 00587CF7 je CBaseView::OnCtlColor+67h (587D07h)> 00587CF9 cmp dword ptr [ebp-0DCh],6> 00587D00 je CBaseView::OnCtlColor+67h (587D07h)> 00587D02 jmp CBaseView::OnCtlColor+0F4h (587D94h)>> entwerder nicht sehr gut zu sein, oder er benutzt die Sprungtabelle nur> wenn nicht im Debug Modus compilert wird, nur weiß ich da nicht, wie ich> die Stelle im Objectcode finden soll, da ja eine Debugging Informationen> mehr drin stecken.>> Oben wird aber mit #1, #3 und #6 verglichen und entsprchend gesprungen> (je) und wenn nichts zutrifft mit jmp in den default-Zweig der> switch-Anweisung.
Ja. Aber eine Sprungtabelle ist in diesem Beispiel auch keine
wirklich gute Lösung.
Zum einen sind deine Werte nicht durchgehend, d.h. die Tabelle
hat Lücken. Ein Compiler muss ja auch darauf achten, dass er
ökonomisch mit dem Speicher umgeht. Wenn in einer Sprungtabelle
von 6 Einträgen nur 3 interessant sind, dann ist das nicht wirklich
ökonomisch. 6 Pointer + Auswertelogik verbrauchen mit ziemlicher
Sicherheit (ich habs jetzt nicht nachgerechnet) schon mehr Speicher
und auch Rechenzeit als obige Verzweigungsleiste.
Diese Sequenz
> 00587CE3 cmp dword ptr [ebp-0DCh],1> 00587CEA je CBaseView::OnCtlColor+0EFh (587D8Fh)> 00587CF0 cmp dword ptr [ebp-0DCh],3> 00587CF7 je CBaseView::OnCtlColor+67h (587D07h)> 00587CF9 cmp dword ptr [ebp-0DCh],6> 00587D00 je CBaseView::OnCtlColor+67h (587D07h)> 00587D02 jmp CBaseView::OnCtlColor+0F4h (587D94h)
durch etwas funktional gleichwertiges mit Tabelle zu ersetzen,
das auch noch schneller ist, wird dir nicht gelingen.
Was müsste gemacht werden
* Es muss getestet werden, ob der Wert > 6 oder < 1 ist
wenn ja -> default anspringen
* Startadresse der Tabelle holen (das sind 6 Adressen im Speicher)
* den Indexwert mit sizeof(Pointer) multiplizieren und zur
Startadresse addieren
* mit der jetzt bekannten Speicheradresse die Sprungadresse aus
der Tabelle holen
* den indirekten Sprung ausführen
Wie du das mit weniger Speicherverbrauch (vergiss die Pointer Tabelle
nicht) und weniger Rechenzeitverbrauch als 3 cmp und 3 je hinkriegen
willst (2 cmp und 2 bedingte Sprünge brauchst du schon mal für
die Überprüfung ob die Eingangszahl sich überhaupt als Tabellen-
index eignet), ist mir ehrlich gesagt ein Rätsel.
Von wegen:
> entwerder nicht sehr gut zu sein
das Gegenteil ist eher der Fall.
Der Compiler kann wesentlich besser optimieren als du.
Du versuchst etwas zu optimieren, was so nicht mehr zu optimieren ist.
> nun ja, dann scheint der Compiler im Visual Studio (ok kein GCC)> entwerder nicht sehr gut zu sein, oder er benutzt die Sprungtabelle nur> wenn nicht im Debug Modus compilert wird,
Oder er hat, wie ich schon geschrieben habe, erkannt, daß es bei so
wenig Werten sinnlos ist. Eine Sprungtabelle besitzt auch einen gewissen
Overhead, und bei gerade einmal drei Werten lohnt sie sich nicht.
@...:
> Im Debug-Modus wird natürlich nicht optimiert.
So natürlich finde ich das nicht. Wie sinnvoll es ist, einen Code zu
debuggen, der anders ist als der Release-Code, darüber kann man
streiten. Vor allem bei Optimierungen, die oft noch so manchen vorher
unbemerkten Fehler zutage fördern.