Forum: Compiler & IDEs Frage an die Compilerbauer


von Ingo L. (corrtexx)


Lesenswert?

Hallo,

ich habe folgenden Testcode:
1
#include <avr/io.h>
2
3
volatile uint16_t Temp,Temp2;
4
5
int main(void)
6
{
7
    while(1)
8
    {
9
    Temp++;
10
    asm volatile ("NOP");
11
    Temp2 = Temp * 60;
12
    asm volatile ("NOP"); 
13
    }
14
}

Bei den unteschiedlichen Optimierungsstufen erhalte ich folgende 
Ausführungsgeschwindigkeiten im Simulator des aktuellen Atmel Studios.

Oo = 22 Takte
O1 = 24 "
O2 = 22 "
O3 = 22 "
Os = 98 "

Warum ist das jetzt bei Os so viel mehr? Die Breakpoints sind jeweils 
bei den NOPs gesetzt.



Ingo

: Verschoben durch Moderator
von Peter II (Gast)


Lesenswert?

Ingo Less schrieb:
> Warum ist das jetzt bei Os so viel mehr?
einfach mal den ASM-Code anschauen.

> Die Breakpoints sind jeweils
> bei den NOPs gesetzt.
da bei der Optimierung auch die Reihenfolge geändert werden kann, können 
die breakpoints jetzt an anderer stelle liegen.

von Dennis S. (eltio)


Lesenswert?

Ich kenne die Antwort nicht, aber wenn ich vor der gleichen 
Fragestellung stehen würde, würde ich mir den erzeugten Assemblercode 
ansehen.

Gruß
Dennis

von SF6 (Gast)


Lesenswert?

Ingo Less schrieb:
> Warum ist das jetzt bei Os so viel mehr?
Bei -Os (Optimize space usage) versucht der Compiler wo geht Daten- und 
Programmspeicher zu sparen, das kann wie du gesehen hast auch dazu 
führen, dass das Programm langsamer wird.

von Ingo L. (corrtexx)


Lesenswert?

Bei Os wird die <__mulhi3> aufgerufen währen bei den anderen Stufen dies 
wohl nicht erfolgt.

O2:
1
    Temp2 = Temp * 60;
2
  58:  20 91 60 00   lds  r18, 0x0060
3
  5c:  30 91 61 00   lds  r19, 0x0061
4
  60:  c9 01         movw  r24, r18
5
  62:  82 95         swap  r24
6
  64:  92 95         swap  r25
7
  66:  90 7f         andi  r25, 0xF0  ; 240
8
  68:  98 27         eor  r25, r24
9
  6a:  80 7f         andi  r24, 0xF0  ; 240
10
  6c:  98 27         eor  r25, r24
11
  6e:  82 1b         sub  r24, r18
12
  70:  93 0b         sbc  r25, r19
13
  72:  88 0f         add  r24, r24
14
  74:  99 1f         adc  r25, r25
15
  76:  88 0f         add  r24, r24
16
  78:  99 1f         adc  r25, r25
17
  7a:  90 93 63 00   sts  0x0063, r25
18
  7e:  80 93 62 00   sts  0x0062, r24

Os:
1
  Temp2 = Temp * 60;
2
  58:  80 91 60 00   lds  r24, 0x0060
3
  5c:  90 91 61 00   lds  r25, 0x0061
4
  60:  6c e3         ldi  r22, 0x3C  ; 60
5
  62:  70 e0         ldi  r23, 0x00  ; 0
6
  64:  06 d0         rcall  .+12       ; 0x72 <__mulhi3>
7
  66:  90 93 63 00   sts  0x0063, r25
8
  6a:  80 93 62 00   sts  0x0062, r24

Speicherverbrauch ist bei OS 132Byte Flash und 4Byte RAM
Speicherverbrauch ist bei O2 138Byte Flash und 4Byte RAM

Somit wird hier für 6Byte weniger Flashverbrauch Faktor 4,5 bei der 
Geschwindigkeit in Kauf genommen!? Krass

von Ingo L. (corrtexx)


Lesenswert?

Daraus schliesse ich, dass wenn es nicht unbedingt auf ein paar Byte 
ankommt man doch lieber O2 nutzen sollte entgegen dem Artikel hier?
AVR-GCC-Codeoptimierung

von Peter II (Gast)


Lesenswert?

Ingo Less schrieb:
> Daraus schliesse ich, dass wenn es nicht unbedingt auf ein paar Byte
> ankommt man doch lieber O2 nutzen sollte entgegen dem Artikel hier?

dann kann schon wieder anders aussehen, wenn du mehrfach eine 
Multiplikation im code hast.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Ingo Less schrieb:
> Somit wird hier für 6Byte weniger Flashverbrauch Faktor 4,5 bei der
> Geschwindigkeit in Kauf genommen!? Krass

In dem Code, den der Compiler hier sieht, beträgt der Unterschied nicht
6, sondern 20 Bytes, und das ist schon ein deutlicher Unterschied. Der
Platzbedarf für die Bibliotheksroutine dürfte schon bei zwei
Multiplikationen dieser Art mehr als kompensiert werden.

: Bearbeitet durch Moderator
von SF6 (Gast)


Lesenswert?

Ingo Less schrieb:
> Somit wird hier für 6Byte weniger Flashverbrauch Faktor 4,5 bei der
> Geschwindigkeit in Kauf genommen!?
Wenn du dem Compiler mit -Os sagst dass er es möglichst klein machen 
soll, dann tut er das auch. Wenn dir das Ergebnis nicht gefällt, dann 
kannst du auch eine andere Optimierung wählen.

von Karl H. (kbuchegg)


Lesenswert?

Ingo Less schrieb:

> Somit wird hier für 6Byte weniger Flashverbrauch Faktor 4,5 bei der
> Geschwindigkeit in Kauf genommen!? Krass

Aber nur dann, wenn dein Programm ausschliesslich aus 16*16 Bit 
Multiplikationen besteht.

von Loocee L. (loocee)


Lesenswert?

Leicht off topic .....

.... aber ich finde es sehr interessant wieviele Comilerbauer es
hier im Forum gibt:

Ingo Less schrieb:
> Frage an die Compilerbauer

Da muss es ja vor (erschaffenen) Compilern nur so wimmeln (wenn
jeder so im Background seine(n) Compiler baut), bzw. hier gibt es
soviel Optimierungspotential dass ja ein ganz toller optimierter
GCC für die AVRs dabei herauskommen muss.....   (   ;-)   )

von Ingo L. (corrtexx)


Lesenswert?

Eberhard F. schrieb:
> Leicht off topic .....
Nein, leicht überflüssig ..... (   ;-)   )

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Eberhard F. schrieb:
> .... aber ich finde es sehr interessant wieviele Comilerbauer es
> hier im Forum gibt:

Ist nicht die erste Frage im Forum, die falsch gestellt war. Und wird 
auch nicht die letzte bleiben. Soll sie deshalb verhungern, weil sich 
niemand zu antworten traut?

PS: Ex-Compilerbauer, falls dich das beruhigt. ;-)

: Bearbeitet durch User
von Rolf Magnus (Gast)


Lesenswert?

Ingo Less schrieb:
> Somit wird hier für 6Byte weniger Flashverbrauch Faktor 4,5 bei der
> Geschwindigkeit in Kauf genommen!? Krass

Ich würde das Programm nicht mit -Os übersetzen, denn welcher AVR hat 
schon weniger als 138 Bytes Flash? Wie, das ist gar nicht das 
"eigentliche" Programm?
Wie so oft wird hier der Fehler gemacht, von einem Trivialprogramm auf 
den Allgemeinfall zu schließen.
Bei größere Programmen ist die Wahrscheinlichkeit nicht so gering, daß 
die Multiplikationsroutine noch öfter gebraucht wird. Sie ist dann nur 
einmal da mit -Os und muss nur aufgerufen werden. Bei -O2 nutzt der 
Compiler dagegen hier eine optimierte Version, da mit einer Konstanten 
multipliziert wird. Die braucht zusätzlichen Code. Da in diesem 
trivialen Programm sonst keinerlei Multiplikation vorkommt, kann die 
Funktion __mulhi3 dafür weggelassen werden. Erst dadurch fällt der 
Unterschied beim Speicherverbrauch so gering aus.

von Linksammler (Gast)


Lesenswert?

Nur der Vollständigkeit halber:

Wenn man das Konsturierte Beispiel etwas realitätsnäher gestaltet, und 
vor allem den Optimizer seine Arbeit machen lässt, dann kommt ein viel 
besseres Resultat heraus.

(Also: Kein Volatile wo's nicht nötig ist. Keine "asm volatile", die 
"zeilen-übergreifende" optimierunen verhindert.)

Also:
1
#include <avr/io.h>
2
3
uint16_t Temp;
4
volatile uint16_t Temp2;
5
6
int main(void)
7
{
8
   Temp=0;
9
    while(1)
10
    {
11
      Temp++;
12
      Temp2 = Temp * 60;
13
    }
14
}

wird zu:
1
.L2:
2
        adiw r24,60
3
        sts Temp2+1,r25
4
        sts Temp2,r24
5
        rjmp .L2

Moral aus der Geschichte: Dem Kompiler keine Knüppel zwischen die Beine 
werfen, dann klappts auch mit der Optimierung.

von Ingo L. (corrtexx)


Lesenswert?

OK,

der eigentlich sinn hintet diesem Banalprogramm war eigentlich, 
herauszufinden ob der Optimizer aus *60 auf einem AVR ohne 
Multiplikationseinheit einen Shift macht.

Nämlich führt Temp2 = Temp * 60 zum selben Ergebnis wie Temp2 = Temp<<6 
- Temp<<2. Die Frage war ob er das selber herausfinden würde. 
Offensichtlich jedoch nicht.

EDIT: Gut, damit hätte ich auch früher rausrücken müssen...

: Bearbeitet durch User
von Jonas B. (jibi)


Lesenswert?

ich würde wette aus *64 hätte er ein Lösung mit einem Shift gebaut ;)

Gruß J

von Ingo L. (corrtexx)


Lesenswert?

Jonas Biensack schrieb:
> ich würde wette aus *64 hätte er ein Lösung mit einem Shift gebaut ;)
Bei O2 ja, bei Os nein

von Yalu X. (yalu) (Moderator)


Lesenswert?

Ingo Less schrieb:
> Nämlich führt Temp2 = Temp * 60 zum selben Ergebnis wie Temp2 = Temp<<6
> - Temp<<2. Die Frage war ob er das selber herausfinden würde.
> Offensichtlich jedoch nicht.

Er macht das sogar noch besser: Nämlich mit
1
uint16_t Temp3 = Temp;
2
Temp2 = ((Temp3 << 4) - Temp3) << 2;

Das liegt dem barrel-shifter-losen AVR sehr viel besser. Beachte
außerdem, auf welch geniale Weise der Shift um 4 realisiert ist.

Edit:

Habe noch einen Klammerfehler und eine kleine Unstimmigkeit korrigiert.

: Bearbeitet durch Moderator
von Peter II (Gast)


Lesenswert?

Jonas Biensack schrieb:
> ich würde wette aus *64 hätte er ein Lösung mit einem Shift gebaut ;)

dann mach doch einfach
1
  Temp2 = (Temp * 64) - (Temp * 4);

von Linksammler (Gast)


Lesenswert?

Peter II schrieb:
> dann mach doch einfach
>   Temp2 = (Temp * 64) - (Temp * 4);

Warum? Um den Compiler zu schlechterem Code zu zwingen?

Wie Yalu geschrieben hat:
der gcc macht das von selbst sogar noch besser, als sich corrtexx das 
für Hand-Optimierten Code ausgedacht hat.

Lasst den Compiler doch einfach seine Arbeit machen. Der kann das schon.

Hand-Optimieren kann man später immer noch, wenn's denn wirklich nötig 
ist.

von Stefan F. (Gast)


Lesenswert?

In meinen Projekten ist das Resultat bei Os und O2 sowohl was 
Performance angeht als auch Speicherbedarf nur wenig Unterschiedlich. 
Ich denke, du hast da durch den abgespeckten Minimal-Code einen 
Extremfall erwischt.

Os würde ich nur im Notfall einsetzen, wenn es wirklich sein muss.

Normal nehme ich O2. Einmal musste ich bei einer bestimmten Version des 
Atmel Compilers auf O1 gehen, ich vermute da einen Bug, weil eben nur 
diese eine Version betroffen war.

O0 nehme ich, wenn ich debuggen möchte.

von Ingo L. (corrtexx)


Lesenswert?

Bisher habe ich ich immer OS genutzt, wie es empfohlen wird. hatte 
bisher auch nie das Bedürfnis etwas von Hand zu optimieren. Wie gesagt, 
hier war nur der Versuch was der Optimizer aus dem *60 macht. Aber er 
macht es ja scheinbar noch besser wie erwartet, auch wenn ich den 
Assemblercode mangels Assemblerkenntnissen nicht so recht nachvollziehen 
kann.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Meiner Erfahrung nach spart man bei Programmen aus der realen Welt mit
-Os prozentual deutlich mehr Flash-Bytes als mit -O2 Zyklen. Da ich
zudem häufiger Flash-Kapazitäts- als Laufzeitprobleme habe, verwende ich
normalerweise -Os.

: Bearbeitet durch Moderator
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter II schrieb:
> dann mach doch einfach
>
>
1
Temp2 = (Temp * 64) - (Temp * 4);

Und was soll da anders sein als mit * 60 ???

*60 in shifts etc. zu expandieren ist nur günstig, wenn es keine MUL 
Befehle gibt.

MUL mit 60 ist dann besser als Rumgeschiebe.

von SF6 (Gast)


Lesenswert?

Denkst du nicht, der Compiler hätte den MUL Befehl eingebaut wenn es 
einen gäbe? Beitrag "Re: Frage an die Compilerbauer"

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Das weiß die Kristallkugel.

Es ist weder Die Compilerversion genannt noch für welches Device 
übersetzt wurde.

Und ja, avr-gcc macht nicht immer den besten denkbaren Code, und das 
gilt auch für den Code, den er für Multiplikationen erzeugt — auch mit 
avr-gcc 4.9 oder 5.

von Rolf M. (rmagnus)


Lesenswert?

SF6 schrieb:
> Denkst du nicht, der Compiler hätte den MUL Befehl eingebaut wenn es
> einen gäbe? Beitrag "Re: Frage an die Compilerbauer"

Der Code dort ist vermutlich für einen attiny übersetzt worden. Wenn man 
stattdessen einen atmega auswählt, wird auch MUL verwendet, und zwar 
sowohl bei -Os als auch bei -O2.
1
        lds r18,Temp
2
        lds r19,Temp+1
3
        mul r20,r18
4
        movw r24,r0
5
        mul r20,r19
6
        add r25,r0
7
        clr __zero_reg__
8
        sts Temp2+1,r25
9
        sts Temp2,r24

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.