PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [MS VC++ 6] Dissassemblierung


Arokh
2006-10-12, 19:08:16
Hi Leute,

ich habe heute mal folgendes ausprobiert. Ich habe in Visual C++ 6 ein kleines Programm geschrieben, das einen einfachen Gauß-Algorithmus, d.h. die Addition aller ganzen Zahlen zwischen k1 und k2, ausführt, und mir dann im Debugger die Dissassemblierung anzeigen lassen, um mal ein Bild davon zu haben, was für Assemblercode VC++ 6 fabriziert. Der C++ Code sah so aus:

int i;
int sum = 0;
int imax = k2;
for (i = k1; i <= imax; i++)
{
sum += i;
}
k1 und k2 sind die beiden ganzen Zahlen, zwischen denen alle Zahlen addiert werden sollen, und die an anderer Stelle festgelegt werden.
Der Assembler-Code sieht jetzt so aus:

0040269E mov ecx,dword ptr [i]
004026A1 add ecx,1
004026A4 mov dword ptr [i],ecx
004026A7 mov edx,dword ptr [i]
004026AA cmp edx,dword ptr [imax]
004026AD jg CGaussDlg::ThreadCalc+0AAh (004026ba)
004026AF mov eax,dword ptr [sum]
004026B2 add eax,dword ptr [i]
004026B5 mov dword ptr [sum],eax
004026B8 jmp CGaussDlg::ThreadCalc+8Eh (0040269e)
So weit ich das verstehe, besagt dieser Code folgendes:

0040269E: schreibe Wert der Variablen in in Register ECX
004026A1: addiere 1 zum Wert in Register ECX
004026A4: schreibe Wert in Register ECX in Variable i zurück
004026A7: schreibe Wert der Variable i in Register EDX
004026AA: vergleiche Wert in Register EDX mit Wert der Variable imax
004026AD: <uninteressant während die Schleife läuft>
004026AF: schreibe den Wert der Variable sum in Register EAX
004026B2: addiere Wert der Variable i zum Wert i Register EAX
004026B5: schreibe Inhalt von EAX in Variable sum
004026B8: kehre zum Schleifeneingang zurück

Aber ist dieser Code nicht furchtbar ineffizient?
Schauen wir uns z.B. Variable i an: i wird zuerst aus dem Speicher in Register ECX geladen, dort um 1 inkrementiert, in den Speicherzurückgeschrieben, wiederum aus dem Speicher in Register EDX geladen und dort endlich mit imax verglichen.
Innerhalb der Schleife ein ähnlicher Aufwand mit Variable sum: aus dem Speicher in Register EAX einlesen, addiert, wieder in den Speicher geschrieben, um beim nächsten Schleifendurchlauf von neuem in EAX geladen zu werden.

Wäre es nicht viel effizienter, dieses ganze Hin- und Hergeschiebe zwischen Speicher und CPU-Registern bleibenzulassen? Könnte man nicht i und sum feste Register (ECX und EAX) zuweisen und während der ganzen Rechenoperation darin belassen. Mir schwebt etwa folgender Assemblercode vor:

004026A1 add ecx,1
004026A4 mov edx,ecx
004026A7 cmp edx,dword ptr [imax]
004026AA jg CGaussDlg::ThreadCalc+0AAh (004026b3)
004026AD add eax,ecx
004026AF jmp CGaussDlg::ThreadCalc+8Eh (004026a1)

man müßte natürlich vorher sicherstellen, daß die Initialwerte von i und sum in den Registern stehen, und hinterher dafür sorgen, daß deren Finalwerte in den Speicher geschrieben werden. Das bräuchte man aber nur jeweils einmal machen, und nicht bei jedem Schleifendurchlauf.
Anmerkung: zum Vergleich mit imax habe ich i dann vorsichtshalber doch in ein zweites Register (EDX) kopiert, da ich annehme, daß die Vergleichsoperation den Registerinhalt überschreibt.

Testweise habe ich den Variablen i, sum und imax bei der Deklaration das Schlüsselwort register vorangestellt, am produzierten Assemblercode änderte sich dadurch aber nichts.

Hab ich in meiner Überlegung einen Denkfehler drin? Oder ist es vielleicht so, daß mir VC++ deswegen so ineffizienten Code erzeugt, weil ich im Debug-Modus bin und alle Optimierung dadurch abgeschaltet sind?

Gast
2006-10-12, 19:16:15
Oder ist es vielleicht so, daß mir VC++ deswegen so ineffizienten Code erzeugt, weil ich im Debug-Modus bin und alle Optimierung dadurch abgeschaltet sind?Klar.

Expandable
2006-10-12, 21:01:26
Schauen wir uns z.B. Variable i an: i wird zuerst aus dem Speicher in Register ECX geladen, dort um 1 inkrementiert, in den Speicherzurückgeschrieben, wiederum aus dem Speicher in Register EDX geladen und dort endlich mit imax verglichen.

Schon mal ++i statt i++ probiert? Ich hab zwar noch nie überprüft, ob das was bringt (eigentlich sollte der Compiler das schon selbst optimieren können), aber ++i macht auch einfach mehr Sinn als i++ an dieser Stelle.

Arokh
2006-10-12, 23:37:24
Schon mal ++i statt i++ probiert? Ich hab zwar noch nie überprüft, ob das was bringt (eigentlich sollte der Compiler das schon selbst optimieren können), aber ++i macht auch einfach mehr Sinn als i++ an dieser Stelle.warum das denn? Zwischen i++ und ++i tritt nur dann ein relevanter Unterschied auf, wenn das Ergebnis der Operation ausgewert wird, also etwa so:
x = i++; // x bekommt den alten Wert von i (von vor der Inkrementierung)
x = ++i; // x bekommt den neuen Wert
Durch diese beiden Konstruktionen:
for (i = x; i < y; i++)
for (i = x; i < y; ++i) bleibt das Ergebnis jedoch unausgewertet. Ausgewertet wird der Wert von i nach der Operation (i++/++i), nicht das Ergebnis der Operation. Deswegen macht es keinen Unterschied.

Mir ist inzwischen auch klar, warum im Debug-Modus ein solcher Umstand betrieben wird: Der Wert jeder Variablen muß nach jeder Manipulation sofort auslesbar sein (um im Debugger angezeigt werden zu können), deswegen muß ihr Wert auch immer sofort in den Speicher geschrieben werden. Würde man den geänderten Wert von z.B. i nur im Register speichern, bliebe der im Speicher stehende Wert solange unverändert, und könnte vom Debugger nicht mehr richtig überwacht werden.

Expandable
2006-10-13, 12:51:37
Der Unterschied ist, dass bei i++ eine temporäre Variable angelegt werden muss (das ist vor allem bei Objekten böse, wo operator++(int) überladen wurde).