Hallo zusammen Ich wollte einmal analysieren, wie GCC ein Programm aufbaut, bei dem in Abhängigkeit von einer Variablen eine bestimmte Funktion aufgerufen wird. Zuerst habe ich ein Programm geschrieben, dass ein Array von Funktions- zeigern nutzt. Hat im Simulator prima geklappt. (Optimierung auf -Os) Dann habe ich den Arrayaufruf durch eine switch case Anweisung ersetzt (siehe unten). Mich interessiert, wie sich jetzt der Maschinencode verändert. Nun das Problem: Das Programm arbeitet im Simulator nicht unter –Os ! Die Zählschleife wird sauber durchgezählt (0,1,2,3) aber es wird nur die Funktion F_2 ausgeführt. Stelle ich die Optimierung auf –O0 läuft es. Das nützt mir aber nichts, da ich auch den Speicherbedarf und die Laufzeit beider Versionen vergleichen möchte. Wie bekomme ich das Programm in –Os im Simulator zum laufen? Woran erkennt der Compiler, dass es eigentlich ein sinnloses Programm ist? Die NOP-Funktion benutze ich als Orientierung in der LSS-Datei. #include <avr/io.h> #define nop asm volatile ("nop") void F_0(char); void F_1(char); void F_2(char); void F_3(char); int main(void) { char i; while(1) { for(i=0; i<4; i++) { nop; switch(i) { case 0 : F_0(i); break; case 1 : F_1(i); break; case 2 : F_2(i); break; case 3 : F_3(i); break; } } } } void F_0 (char x0) { nop; x0++; x0 = x0>>2; PORTA = x0; } void F_1 (char x1) { nop; x1 += 5; PORTB = x1; } void F_2 (char x2) { nop; x2++; x2 += x2 | 0b00001111; PORTC = x2; } void F_3 (char x3) { nop; x3++; x3 &= 0xF0; PORTD = x3; }
Ist hier völlig ok. Der Compiler inlined die Funktionen und sortiert das sinnlose Variablengewusel komplett aus. Bleiben die Ausgabebefehle mit konstanten Daten und die NOPs übrig. Diese Runde im klassischen Spiel Compiler gegen Programmierer ging jedenfalls klar an den Compiler.
Danke erste einmal Bleiben für mich zwei Fragen: 1) Warum wird bei meiner Lösung mit dem Array nichts aussortiert? (Programm siehe unten) 2) Woran will der Compiler die Sinnlosigkeit erkennen? Ich hab schon viel analysiert. Führt man das Ergebnis einer Berechnung auf ein Hardware-Port / Zähler u.s.w sortiert er i.n.R. nicht aus. Mein Programm mit dem Array: #include <avr/io.h> #define nop asm volatile ("nop") typedef void (*pFunc)(char); void F_0(char); void F_1(char); void F_2(char); void F_3(char); pFunc Arr[4] = {F_0, F_1, F_2, F_3}; int main(void) { unsigned char i; while(1) { for(i=0; i<4; i++) { nop; Arr[i](i); } } } void F_0 (char x0) { nop; x0++; x0 = x0>>2; PORTC = x0; } void F_1 (char x1) { nop; x1 += 5; PORTC = x1; } void F_2 (char x2) { nop; x2++; x2 += x2 | 0b00001111; PORTC = x2; } void F_3 (char x3) { nop; x3++; x3 &= 0xF0; PORTC = x3; }
Ganymed schrieb: > Woran erkennt der Compiler, dass es eigentlich ein sinnloses Programm > ist? Inlining der Funktionen. Wenn klein genug, wird der Code direkt eingebaut und nicht als Funktion aufgerufen. Damit ist er dem beim GCC recht weit entwickelten Optimizer zugänglich und das Ergebnis sieht man. Abhilfe: -fno-inline-small-functions.
Ganymed schrieb: > 1) Warum wird bei meiner Lösung mit dem Array nichts aussortiert? > (Programm siehe unten) Weil der Compiler die Funktionen dieses Mal nicht inlinen kann. > Ich hab schon viel analysiert. Führt man das Ergebnis > einer Berechnung auf ein Hardware-Port / Zähler u.s.w > sortiert er i.n.R. nicht aus. Bei steigender Komplexität einer Funktion ist irgendwann die Schwelle zum nicht mehr sinnvollem Inlining überschritten. Diese Kalkulation ist allerdings alles andere als perfekt. Zumal er diese Entscheidung möglicherweise vor der Optimierung trifft.
Ganymed schrieb: > 1) Warum wird bei meiner Lösung mit dem Array nichts aussortiert? > (Programm siehe unten) Die Werte in Arr[] sind zur Compilezeit nicht bekannt. Es ist denkbar, daß sie vor der Verwendung in main in einem anderen Modul verändert werden. Um die Optmierung prinzipiell zu ermöglichen müsste Arr[] read-only sein, also const. Allerdings ist selbst dann einiges zu tun für einen Compiler, um das dann zu optimiern: -- die i-Schleife muss aufgerollt werden -- es muss erkannt werden, daß Arr[i] zur Compilezeit bekannte werte hat -- Die indirekten Aufrufe Arr[i]() müssten in direkte umgewandelt werden -- ggf. Arr[i] inlinen -- weiter optimieren GCC 4.3 ist noch nicht so weit. Johann
>GCC 4.3 ist noch nicht so weit.
Aber schon verdammt weit! Hut ab vor den
Programmieren kann ich da nur sagen.
Das mit dem Inlinimg der Funktionen ist
mir jetzt klar und habe ich in meinem
Code auch schon beobachtet.
Warum aber, in meinem switch-case-Prog.,
die Funktion F_3 aussortiert wird und die F_2
nicht, ist mir immer noch nicht klar.
Die Funktionen haben die gleiche "Kömplexität"
und greifen auf verschiedene Ports zu.
F2: x2 += x2 | 0b00001111; Das sind 2 Operationen: temp = x2 | const; x2 = x2 + temp; F3: x3 &= 0xF0; Und das ist nur eine Operation.
Ganymed schrieb: > Warum aber, in meinem switch-case-Prog., > die Funktion F_3 aussortiert wird und die F_2 > nicht, ist mir immer noch nicht klar. > Die Funktionen haben die gleiche "Kömplexität" > und greifen auf verschiedene Ports zu. Nein, F2 ist komplexer. Es hat mehr Operationen. Nach den Tree-Passes sehen F2/F3 so aus:
1 | ;; Function F_2 (F_2) |
2 | |
3 | F_2 (x2) |
4 | {
|
5 | char x2.28; |
6 | unsigned char D.1254; |
7 | |
8 | <bb 2>: |
9 | __asm__ __volatile__("nop"::); |
10 | x2.28 = x2 + 1; |
11 | D.1254 = (unsigned char) (x2.28 | 15) + (unsigned char) x2.28; |
12 | *53B ={v} D.1254; |
13 | return; |
14 | }
|
15 | |
16 | ;; Function F_3 (F_3) |
17 | |
18 | F_3 (x3) |
19 | {
|
20 | char x3.34; |
21 | unsigned char x3.5; |
22 | |
23 | <bb 2>: |
24 | __asm__ __volatile__("nop"::); |
25 | x3.34 = x3 + 1; |
26 | x3.5 = (volatile uint8_t) (x3.34 & -16); |
27 | *50B ={v} x3.5; |
28 | return; |
29 | }
|
Dies führt dazu, daß F2 durch 11 Insns dargestellt wird, F3 jedoch durch nur 10 Insns. Wenn du wirklich nachverfolgen willst, wie gcc an dem Code rumbastelt, kannst du Dumps erzeugen lassen mit.
1 | -fdump-tree-all-details |
2 | -fdump-rtl-all-details |
3 | -save-temps |
4 | -fverbose-asm |
5 | -dP |
Wenn -dP zu viel des Guten ist im Assembler (*.s), dann einfach weglassen oder -dp angeben stattdessen. Die Tree-Dumps sind C-ähnlich und lassen sich problemlos verstehen. Falls Fragen zu RTL sind kann ich die gerne beantworten. Johann
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.