PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : GCC deoptimiert



p_mork
25.05.2007, 20:19
Hallo,

ich habe eine Funktion add(char,char), die mir die Summe der beiden Werte liefert. Nun hat GCC 3.4.6 mit der Optimierung 2,3,s folgendes draus gemacht:


35 .global _Z3addcc
37 _Z3addcc:
38 .LFB42:
39 .LM1:
40 /* prologue: frame size=0 */
41 /* prologue end (size=0) */
42 0000 962F mov r25,r22
43 .LBB2:
44 .LM2:
45 0002 980F add r25,r24
46 .LBE2:
47 .LM3:
48 0004 892F mov r24,r25
49 0006 9927 clr r25
50 /* epilogue: frame size=0 */
51 0008 0895 ret


Es kommt aber noch dicker, Code bei Optimierung 0:


32 .global _Z3addcc
34 _Z3addcc:
35 .LFB42:
36 .LM1:
37 /* prologue: frame size=2 */
38 0000 CF93 push r28
39 0002 DF93 push r29
40 0004 CDB7 in r28,__SP_L__
41 0006 DEB7 in r29,__SP_H__
42 0008 2297 sbiw r28,2
43 000a 0FB6 in __tmp_reg__,__SREG__
44 000c F894 cli
45 000e DEBF out __SP_H__,r29
46 0010 0FBE out __SREG__,__tmp_reg__
47 0012 CDBF out __SP_L__,r28
48 /* prologue end (size=10) */
49 0014 8983 std Y+1,r24
50 0016 6A83 std Y+2,r22
51 .LBB2:
52 .LM2:
53 0018 9981 ldd r25,Y+1
54 001a 8A81 ldd r24,Y+2
55 001c 890F add r24,r25
56 001e 9927 clr r25
57 .LBE2:
58 /* epilogue: frame size=2 */
59 0020 2296 adiw r28,2
60 0022 0FB6 in __tmp_reg__,__SREG__
61 0024 F894 cli
62 0026 DEBF out __SP_H__,r29
63 0028 0FBE out __SREG__,__tmp_reg__
64 002a CDBF out __SP_L__,r28
65 002c DF91 pop r29
66 002e CF91 pop r28
67 0030 0895 ret


Bei der Optimerung 1 kommt jedoch das hier raus:


32 .global _Z3addcc
34 _Z3addcc:
35 .LFB42:
36 .LM1:
37 /* prologue: frame size=0 */
38 /* prologue end (size=0) */
39 .LBB2:
40 .LM2:
41 0000 860F add r24,r22
42 .LBE2:
43 .LM3:
44 0002 9927 clr r25
45 /* epilogue: frame size=0 */
46 0004 0895 ret

Kann mir jemand erklären, wieso GCC bei Opt. 0,2,3,s solch einen Mist produziert? Besonders bei Opt. 0, wo er anscheinend aus dem Hw-Stack einen Sw-Stack macht ???

MfG Mark

kater
26.05.2007, 10:35
Hm. also ich kann leider kein Assembler. Ich wuerde sagen, dass ist nur aus deiner Sicht Mist. Opt.0 ist btw ueberhaupt keine Optimierung, deswegen ist dieser Code auch am längsten.
Welche Stufe jetzte fuer max. Geschwindigkeit, Speed oder beides steht, weiss ich leider nicht auswendig. Aber ich kann mir eben gut vorstellen, das ein auf Geschwindigkeit optimierter Code eben haesslich aussieht. Verstehste?

p_mork
26.05.2007, 11:12
Hm. also ich kann leider kein Assembler. Ich wuerde sagen, dass ist nur aus deiner Sicht Mist.

Nein, das ist wirklich Mist, denn der Code bei der Oprimierungsstufe 1 ist der schnellste, der kürzeste und der einfachste. Im grunde muss die Funktion nichts anderes tun als r24 mit r22 zu addieren und zurückkehren. Beim Code mit der Opt. 2,3,s wird zuerst r22 nach r25 kopiert, dann wird r25 mir r24 addiert und das Ergebnis dann nach r24 kopiert. Man hätte aber wie bei Opt.1 die beiden Reg einfach sofort addieren können. Bei der opt. 0 blick ich ehrlich gesagt kaum durch, denn dort wird der Stack, für den es die Befehle push und pop gibt manuell angesteuert, das heisst Stackpointer wird zuert in ein Register geladen, dekrementiert und dann wieder in die IO-Register SP gespeichert. Warum in allen drei Fällen r25 gelöscht wird weiss ich auch nicht, aber ich neme an, dass es mit der späteren Registerverwendung des GCC zusammenhängt.

MfG Mark

kater
26.05.2007, 11:19
Dann ist dein Code einfach zu einfach, zu wenig. Da weiss der gcc nicht was es machen soll, aber da es was machen muss, macht er scheissr ;)

izaseba
26.05.2007, 12:30
Warum in allen drei Fällen r25 gelöscht wird weiss ich auch nicht, aber ich neme an, dass es mit der späteren Registerverwendung des GCC zusammenhängt.
Diese Frage beantwortet hoffentlich ein Auszug aus der avr-libc Dokumentation:


Return values: 8-bit in r24 (not r25!), 16-bit in r25:r24, up to 32 bits in r22-r25, up to 64 bits in r18-r25. 8-bit return values are zero/sign-extended to 16 bits by the caller (unsigned char is more efficient than signed char - just clr r25).

Quelle:http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_reg_usage

Tip: auf www.mikrocontroller.net liest Jörg Wünsch mit, wenn er Dir die Fragen nicht beantworten kann, wer sonst ? :wink:

Gruß Sebastian

p_mork
26.05.2007, 13:56
Hallo izaseba,

danke für die Info. Zumindest das mit r25 ist jetzt geklärt. Ich werd mal die Frage auf Mikrocontroller.net stellen, hoffentlcih bekomm ich da eine Antwort drauf, wieso der Code so lang ist.

MfG Mark

PicNick
26.05.2007, 16:55
Der Code bei "0" stellt den Standard dar. Die Call argumente werden auf den Stack gestellt, dadurch sind sie für alle folgenden Befehle in der Funktion an einer definierten Stelle ( y+1, y+2). (Bei anderen Compilern macht das oft schon der "CALL" und nicht die Funktion selbst)

Die Optimierung checkt aber dann den konkreten Einzelfall. Und kommt drauf, daß obiges eben nicht erforderlich ist. Ja, und dann bleibt eben nurmehr das bißchen Zeugs über, wie in deinem Beispiel bei Opt "1"

Eine weitere Optimerung schmeißt vielleicht überhaupt die ganze Funktion weg und macht die Addition direkt dort, wo jetzt der "Call" steht. Das wär dann noch flotter.

Wie schon von anderen gesagt: Der "lange" Code ist der standard-Call-mechanismus, der schaut bei einem 8-Bit Miniwuzi eben so aus.

SprinterSB
27.05.2007, 09:19
Das "clr 25" geht auf das ABI von avr-gcc zurück, weil eine return value promotion gemacht wird, also Werte als 16-Bit zurückgegeben werden.

Diese Promotion wird selbst dann ausgeführt, wenn die Funktion geinlinet wird.

Die überflüssigen mov-Instruktionen bei O1, O2 und Os gehen darauf zurück, daß GCC nicht perfekt ist.

Zeig mal deinen Quellcode.

p_mork
27.05.2007, 11:29
Hallo,

danke für Eure Antworten. Der Quellcode ist folgender:

#include <iostream.h>
#include "MyCode.h"

char add(char a,char b)
{
return a+b;
}

using std::cout;

int main()
{
System_Init();
Uart_Init(38400);
while(1)
{
cout<<static_cast<int>(add(10,20))<<endl;
}
}


Ich weiss, die Funktion macht keinen Sinn. Ich wollte einfach mal versuchen, eine Funktion in ASM zu schreiben und sie dann mit dem C++-Code zu compilieren. Als dann der Linker meckerte, weil er die Funktion nicht gefunden hat, hab ich es in C++ gemacht, um zu gucken, wie sie heissen mußte, damit sie auch erkannt wird. Jetzt weiss ich, dass sie '_Z3addcc' heißen muss, um sie als char add(char,char) verwenden zu können.

MfG Mark

izaseba
27.05.2007, 12:08
Nun hab ich mir das selber angeschaut, allerdings in C mit dem Ergebnis kann ich garnicht meckern, Code:

#include <avr/io.h>


uint8_t add(uint8_t a,uint8_t b) {
return a+b;
}

int main(void) {

uint8_t a=15;
uint8_t b=10;
DDRC = 255;

PORTC = add(a,b);
while(1);
return 0;
}
Das wird aus add gemacht:


uint8_t add(uint8_t a,uint8_t b) {
8e: 68 0f add r22, r24
return a+b;
}
90: 86 2f mov r24, r22
92: 99 27 eor r25, r25
94: 08 95 ret
Und da alles zu Kompileirzeit bekannt ist wird die Funktion garnicht aufgerufen:


PORTC = add(a,b);
a2: 89 e1 ldi r24, 0x19 ; 25
a4: 85 bb out 0x15, r24 ; 21
a6: ff cf rjmp .-2 ; 0xa6 <main+0x10>


Das einzigste was hier auffällt, man könnte die Funktion add komplett wegoptimieren, dann wäre es perfekt, aber man kann ja nicht alles haben ;-)
Dann hab ich mir gedacht, ich lese ein Port ein, damit er doch add aufruft:


int main(void) {

uint8_t b=10;
uint8_t c;
DDRC = 255;
c=PINB;
PORTC = add(c,b);
while(1);
return 0;
}
Und? Käse, selbst hier ruft er die nicht auf !


c=PINB;
a2: 86 b3 in r24, 0x16 ; 22
PORTC = add(c,b);
a4: 86 5f subi r24, 0xF6 ; 246
a6: 99 27 eor r25, r25
a8: 85 bb out 0x15, r24 ; 21
aa: ff cf rjmp .-2 ; 0xaa <main+0x14>


Achso, optimiert wurde mit -Os was anderes benutze ich sowieso nicht.

Gruß Sebastian

P.S. avr-gcc 4.1.0

p_mork
27.05.2007, 13:40
Hallo izaseba,

P.S. avr-gcc 4.1.0

Asoo! Ich hab noch avr-gcc 3.4.6. Aber ich hab schon öfters gehört, dass avr-gcc 4.1.0 noch in Entwicklungphase ist und deshalb Bugs enthalen kann. Trifft das im Moment immer noch zu?

MfG Mark

izaseba
27.05.2007, 15:37
noch in Entwicklungphase ist und deshalb Bugs enthalen kann.
gcc 4.*.* hat schon seit ein paar Tagen den unstable Zweig verlassen.
Ich war mir nicht sicher welche Version bei Winavr aktuell enthalten ist, aber hier (http://sourceforge.net/forum/forum.php?forum_id=699825) kannst Du selber nachlesen ;-)

Gruß Sebastian

SprinterSB
28.05.2007, 10:04
Was die GCC-Version angeht, kann man mit 3.4.6 prima leben. Ich hab mal n Code vergleich gegen die 4.1.x gemacht, und der Code in der 4-er war nicht so dicht wie in der 3-er!

Momentan gab es da vor allem interne Umstellungen auf SSA, weil diese Darstellung sehr gut geeignet ist, um hardware-unabhängige Optimierungen zu machen. Momentan wird der Code innerhalb von GCC 4 also folgendwermassen transformiert:

C/C++/Java/... --> tree --> SSA --> tree --> RTL --> asm



Das einzigste was hier auffällt, man könnte die Funktion add komplett wegoptimieren, [...]

[-X

Nein. Die Funktion könnte von einem anderen Modul aufgerufen werden. Sie wegzulassen könnte also zu einem Fehler zur Linkzeit führen!

Wenn die Funktion nur in diesem Modul auftaucht, dann kann man sie als "static" deklarieren und siehe, gcc wirft sie weg bzw. warnt an, daß da was unnötiges steht. Ganz wie erwünscht...

izaseba
28.05.2007, 11:02
Was die GCC-Version angeht, kann man mit 3.4.6 prima leben. Ich hab mal n Code vergleich gegen die 4.1.x gemacht, und der Code in der 4-er war nicht so dicht wie in der 3-er!
Hallo Sprinter, schön Dich wieder zu sehen :-)
Ich hab auch nirgendwo was anderes behauptet 8-[


Nein. Die Funktion könnte von einem anderen Modul aufgerufen werden. Sie wegzulassen könnte also zu einem Fehler zur Linkzeit führen!
Stimmt, soweit hab ich nicht gedacht :-k Der Kompiler kann wirklich nicht wissen, ob man die Funktion noch irgendwo anders aufruft.

Fazit:
Es klappt doch ganz gut mit der Optimierung, und -O0 wird normallerweise kein Mensch brauchen, außer zu Debugzwecken, oder ?

Gruß Sebastian