Forum: Compiler & IDEs Compiler verhält sich seltsam


von Ingo L. (corrtexx)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe das Thema "Compiler" von gestern hier nochmal aufgearbeitet.
Ganz simples Programm: Es wird etwas an den IOs eingestellt und etwas an 
den Timerm gemacht. Wenn ich die Funktion init_AVR nicht static 
deklariere, erzeugt der Compiler ineffizienteren Code als mit static. 
Nämlich z.B. hier:
ohne static
1
  // Input Capture
2
  TIFR1 |= (1<<ICF1);
3
  ac:  b5 9a         sbi  0x16, 5  ; 22
4
  TIMSK1 |= (1<<ICIE1);
5
  ae:  ef e6         ldi  r30, 0x6F  ; 111
6
  b0:  f0 e0         ldi  r31, 0x00  ; 0
7
  b2:  80 81         ld  r24, Z
8
  b4:  80 62         ori  r24, 0x20  ; 32
9
  b6:  80 83         st  Z, r24
10
  TCCR1B |= (1<<ICNC1);
11
  b8:  e1 e8         ldi  r30, 0x81  ; 129
12
  ba:  f0 e0         ldi  r31, 0x00  ; 0
13
  bc:  80 81         ld  r24, Z
14
  be:  80 68         ori  r24, 0x80  ; 128
15
  c0:  80 83         st  Z, r24
16
  TCCR1B |= (1<<CS11);      // Prescaler 8
17
  c2:  80 81         ld  r24, Z
18
  c4:  82 60         ori  r24, 0x02  ; 2
19
  c6:  80 83         st  Z, r24
20
  c8:  08 95         ret
mit static:
1
// Input Capture
2
  TIFR1 |= (1<<ICF1);
3
  ac:  b5 9a         sbi  0x16, 5  ; 22
4
  TIMSK1 |= (1<<ICIE1);
5
  ae:  80 91 6f 00   lds  r24, 0x006F
6
  b2:  80 62         ori  r24, 0x20  ; 32
7
  b4:  80 93 6f 00   sts  0x006F, r24
8
  TCCR1B |= (1<<ICNC1);
9
  b8:  80 91 81 00   lds  r24, 0x0081
10
  bc:  80 68         ori  r24, 0x80  ; 128
11
  be:  80 93 81 00   sts  0x0081, r24
12
  TCCR1B |= (1<<CS11);      // Prescaler 8
13
  c2:  80 91 81 00   lds  r24, 0x0081
14
  c6:  82 60         ori  r24, 0x02  ; 2
15
  c8:  80 93 81 00   sts  0x0081, r24
Kann mir jemand sagen warum das so ist?
Hier nochmal der Output:
Building file: .././main.c
    Invoking: AVR/GNU C Compiler : 4.9.2
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-gcc.exe"  -x c 
-funsigned-char -funsigned-bitfields -DDEBUG -DF_CPU=8000000  -Os 
-ffunction-sections -fdata-sections -fpack-struct -fshort-enums -g2 
-Wall -mmcu=atmega328p -c -std=gnu99 -MD -MP -MF "main.d" -MT"main.d" 
-MT"main.o"   -o "main.o" ".././main.c"
    Finished building: .././main.c
    Building target: 1V0.elf
    Invoking: AVR/GNU Linker : 4.9.2
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-gcc.exe" -o 
1V0.elf  main.o   -Wl,-Map="1V0.map" -Wl,--start-group -Wl,-lm 
-Wl,--end-group -Wl,--gc-sections -mmcu=atmega328p
    Finished building target: 1V0.elf
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-objcopy.exe" -O 
ihex -R .eeprom -R .fuse -R .lock -R .signature -R .user_signatures 
"1V0.elf" "1V0.hex"
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-objcopy.exe" -j 
.eeprom  --set-section-flags=.eeprom=alloc,load --change-section-lma 
.eeprom=0  --no-change-warnings -O ihex "1V0.elf" "1V0.eep" || exit 0
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-objdump.exe" -h 
-S "1V0.elf" > "1V0.lss"
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-objcopy.exe" -O 
srec -R .eeprom -R .fuse -R .lock -R .signature -R .user_signatures 
"1V0.elf" "1V0.srec"
    "C:\Users\ingo\Downloads\Atmel 
Studio\Toolchain\Atmel_AVR_Toolchain_3.5.0.1662\bin\avr-size.exe" 
"1V0.elf"
       text     data      bss      dec      hex  filename
        210        0        0      210       d2  1V0.elf
  Done executing task "RunCompilerTask".
  Task "RunOutputFileVerifyTask"
        Program Memory Usage   :  210 bytes   0,6 % Full
        Data Memory Usage     :  0 bytes   0,0 % Full

: Bearbeitet durch User
von Carl D. (jcw2)


Lesenswert?

(sinngemäß)
> wenn man eine Funktion static macht, erzeugt der Compiler besseren Code

Ja, deshalb gibt es static. Es sagt nämlich:
 "außer in dieser Übersetzungseinheit wird diese Funktion nirgends 
gebraucht"
Damit kann man sie problemlos inlinen und wenn sie nur einmal benutzt 
wird, spart man sich CALL/RET inklusive der 
Register-Sicherungskonventionen.
Erste Optimierungsregel: sag dem Compiler genau was du tun willst.

(ok, zugegeben, in deinem Beispiel stellt sich derGCC schon etwas an. 
Das war wirklich beides -Os?)

: Bearbeitet durch User
von Ingo L. (corrtexx)


Lesenswert?

Carl D. schrieb:
>> wenn man eine Funktion static macht, erzeugt der Compiler besseren Code
Eigentlich war hier eher die Frage, warum er die Registerzugriffe über 
den Z-Pointer macht.

Carl D. schrieb:
> Das war wirklich beides -Os?)
Jepp

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Na ja, warum er da ohne static auf die Register indirekt zugreift,  mit 
static dagegen direkt, hat mit der Sichtbarkeit der Funktion nichts zu 
tun.

Allerdings gilt hier eine ganz pragmatische Lebensregeln: Frag einfach 
niemals, warum ein Compiler das macht, was er macht, schon gar nicht 
beim gcc (außer, du bist Compilerbauer). Es lohnt sich schlicht nicht, 
und vergeudet nur unnütze Lebenszeit.

Oliver

: Bearbeitet durch User
von Ingo L. (corrtexx)


Lesenswert?

Oliver S. schrieb:
> Allerdings gilt hier eine ganz pragmatische Lebensregeln: Frag einfach
> niemals, warum ein Compiler das macht, was er macht, schon gar nicht
> beim gcc (außer, du bist Compilerbauer). Es lohnt sich schlicht nicht,
> und vergeudet nur unnütze Lebenszeit.
Ich dachte mir, dass genau solche kleinen Finessen den Compiler über 
kurz oder lang verbessern... Es muss ja nicht immer ein  Fehler sein, 
den man verbessert. Ich sehe sowas eher als eine Art Optimierung.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Dieses Verhalten ist mir auch schon aufgefallen.

Meine bisherige Analyse: Die Optimierung, statt über das Z-Register
direkt mit LDS/STS auf das Register zuzugreifen, scheint nur dann
stattzufinden, wenn die betreffende Funktion mindestens eine Verzweigung
enthält.

Die Funktion init_AVR enthält keine Verzweigung, also wird dort der
Zugriff nicht optimiert. Durch die Static-Deklaration wird die Funktion
init_AVR aber gar nicht erzeugt, sondern stattdessen der entsprechende
Programmcode direkt in main eingebaut. Da main aber eine Verzweigung in
Form der While-Schleife enthält, wird hier der Zugriff optimiert.

Durch die fehlende Optimierung wird der Programmcode nicht größer, es
werden nur zwei zusätzliche Taktzyklen pro Registerzugriff gebraucht. In
einer Init-Funktion, die nur ein einziges Mal aufgerufen wird, ist das
nicht weiter tragisch.

Ansonsten enthält jede größere Funktionen i.Allg. mindestens eine
Verzweigung. Bei kleineren zeitkritischen Funktionen ist man sowieso
bestrebt, diese vom Compiler inlinen zu lassen, und die Chance ist groß,
dass dann die übergeordnete Funktion eine Verzweigung enthält.

Erfolgt nur ein einzelner Lese- oder Schreibzugriff auf das Register,
wird immer optimiert. Erfolgen mehr als zwei Zugriffe auf dasselbe
Register, ist die Verwendung des Z-Registers zum Zwischenspeichern der
Adresse durchaus sinnvoll, da dies kürzerem Code ergibt.

Das seltsame Verhalten des Compiler ist also nur in seltenen Fällen ein
Problem.

Mir fällt spontan nur ein einziger Fall ein, wo dieses Verhalten stört,
nämlich in Interrupthandlern. Diese sind zwar i.Allg. kurz, können aber
trotzdem nicht geinlinet werden. Auf Grund ihrer Einfachheit enthalten
sie oft auch keine Verzweigungen. Durch die fehlende Optimierung werden
nicht nur die oben erwähnten zusätzlichen zwei Taktzyklen benötigt, da
die Register R30 und R31 auf dem Stack gesichert werden müssen. Das
kostet weitere 8 Zyklen plus 8 Bytes Flash. In besonders zeitkritischen
Fällen könnte man deswegen darüber nachdenken, in den Interrupthandler
eine Dummy-Verzweigung einzubauen.

Vielleicht kommt ja Johann noch vorbei und kann aus Expertensicht etwas
dazu sagen.

von Carl D. (jcw2)


Lesenswert?

wo man den Compiler definitiv unterstützen kann, ist wenn man als 
volatile deklarierte Register (alle HW-Register) nicht bitweise füllt.
Wenn man alle Timer-Register initialisiert, dann darf man die Werte 
zuerst ver-oder-n und dann zuweisen. Im anderen fall sagt man: "bitte in 
diesem sich jederzeit ändernden Register ein Bit setzen", also 
Read/Modify/Write. Und so macht er das dann eben.

Das mit dem Z-Pointer könnte auch daher kommen, daß eine Funktion R30/31 
frei benutzen darf. Der Aufrufende muß diese sichern, falls er sie noch 
braucht. Als inline-Funktion sieht dann die Kostenberechnung ganz anders 
aus. Und -Os bedeutet "Size", was offenbar als "Speed egal" gedeutet 
wird. Aus Size-Sicht sind beide gleichwertig, vermutlich gewinnt der 
Erst-Beste ;-)

von Oliver S. (oliverso)


Lesenswert?

Ingo L. schrieb:
> Ich dachte mir, dass genau solche kleinen Finessen den Compiler über
> kurz oder lang verbessern... Es muss ja nicht immer ein  Fehler sein,
> den man verbessert. Ich sehe sowas eher als eine Art Optimierung.

Der Compiler kann deine Beiträge leider nicht lesen, und sich von selber 
verbessern kann er auch nicht. Der offizielle Weg wäre also die 
Erstelling eines Bug-Reports, mit der Hoffnung, daß sich einer der 
Compilerentwickler der Sache annimmt.

Leider gibt es die für den AVR-Gcc nicht wirklich. Johann L. 
ausdrücklich ausgenommen (ohne ihn wäre der AVR-gcc wohl noch auf dem 
Stand des letzen Jahrtausends), aber der kann die Welt auch nicht 
alleine retten.

Also bleibt für dich nur Plan B: gcc-Sourcen besorgen, verstehen, selber 
verbessern...

Oliver

: Bearbeitet durch User
von Jan (Gast)


Lesenswert?

Leider sind Bugreports beim GCC nur wegen "Optimierungspotenzial" 
ziemlich aussichtslos. Das liegt natürlich hauptsächlich am kleinen 
Entwicklerstab und man kann es auch nachvollziehen, dass wegen ein paar 
Bytes dann nix gemacht wird.
Ich habe da auch schon einige Merkwürdigkeiten gefunden.
Was ich aber gemerkt habe: Mit neueren Versionen ändert sich das 
Verhalten durchaus mal. Auch wenn's nicht explizit irgendwo beschrieben 
ist. Einfach mal ein bisschen rumtesten. Teste mal eine 5er Version. 
Auch mit Optimierungsparametern spielen.

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
Noch kein Account? Hier anmelden.