Forum: Compiler & IDEs Codeblähungen beim Rechnen mit globaler Variable


von Kurt Schluss (Gast)


Lesenswert?

Hallo,

ich verwende avr-gcc zur Kompilierung für einen Atmega.

Ich habe jetzt schon mehrfach beobachtet, dass es den Code aufbläht, 
wenn man mit globalen Variablen rechnet, anstatt mit temporären, lokalen 
Variablen, die man dann am Ende der globalen Variable zuweist.

Warum ist das so? Sollte der Compiler nicht eigentlich sowas automatisch 
optimieren? Ich finde, es macht die Sache auch immer etwas 
unübersichtlich.
Wenn die globale Variable volatile wäre, wäre es das ja einleuchtend. 
Ist sie aber in dem Fall gar nicht mal.

von (prx) A. K. (prx)


Lesenswert?

Kurt Schluss schrieb:
> Sollte der Compiler nicht eigentlich sowas automatisch
> optimieren?

Kann er aber nur nur dann, wenn er genau weiss, dass zwischenzeitlich 
niemand auf die Variable zugreift. Funktionsaufrufe und 
Aliasing-Potential können ihn daran hindern.

von Peter D. (peda)


Lesenswert?

Kurt Schluss schrieb:
> Warum ist das so? Sollte der Compiler nicht eigentlich sowas automatisch
> optimieren?

Der AVR-GCC erzeugt schon recht brauchbaren Code, macht aber einige 
unnötige Umstände.
Ich nehme auch oft lokale Zwischenvariablen, um ihm auf die Sprünge zu 
helfen.
Insbesondere bei Pointerzugriffen lohnt sich das sehr.

Wenn Du was besseres willst, nimm den IAR.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> macht aber einige unnötige Umstände.

Naja, das solltest du schon näher erläutern.

Die Antwort vor deiner trifft die Sache wohl deutlich genauer.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Naja, das solltest du schon näher erläutern.

Z.B. hier sind die 2 RJMP überflüssig:
1
void foo( uint8_t val )
2
{
3
  uint8_t i = 45;
4
  if( val & 0x08 )
5
  44:  83 ff         sbrs  r24, 3
6
  46:  02 c0         rjmp  .+4        ; 0x4c <foo+0x8>
7
  48:  8b e7         ldi  r24, 0x7B  ; 123
8
  4a:  01 c0         rjmp  .+2        ; 0x4e <foo+0xa>
9
  4c:  8d e2         ldi  r24, 0x2D  ; 45
10
    i = 123;
11
  PORTB = i;
12
  4e:  88 bb         out  0x18, r24  ; 24
13
}
14
  50:  08 95         ret

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Z.B. hier sind die 2 RJMP überflüssig:

Hat aber nichts mit einer globalen Variablen zu tun.

Die Ursachen, warum beim Zugriff auf eine globale Variable der Code
umständlicher wird im Vergleich zur lokalen, beschreibt die erste
Antwort recht gut.  Wenn man's genauer haben will, müsste man den Code
im Detail sehen.

(Das soll nicht heißen, dass deine Bemerkung falsch wäre, aber die
müssten wir in einem separaten Thread diskutieren.)

von (prx) A. K. (prx)


Lesenswert?

Wer mal sehen will, auf welche skurrilen Lösungen Compiler kommen 
können, der sollte sich mal das Resultat für amd64 ansehen:
        andl    $8, %edi
        cmpb    $1, %dil
        sbbl    %eax, %eax
        andl    $-78, %eax
        addl    $123, %eax
        movb    %al, PORTB(%rip)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> der sollte sich mal das Resultat für amd64 ansehen

Das wär' doch mal was für Moby. ;-)

von (prx) A. K. (prx)


Lesenswert?

Das lag mir natürlich auf der Zunge, denn darauf kommt ein 
Asm-Programmierer nicht so leicht. Ich liess es aber lieber weg bevor 
der Zirkus hier auch noch losgeht. Ist auch nicht wirklich optimal, auf 
Basis von CMOV gehts besser.

Das Problem bei AVR (und auch Thumb, nicht aber ARM/Thumb2) liegt evtl. 
darin, dass der Compiler die Initialisierung wegschiebt und auf einen 
conditional select hinarbeitet, dann aber keinen hat. Also auf
  r = (cond) ? in1 : in2;
statt
  r = in1;
  if (cond) r = in2;

Nimmt man statt "45" einen Parameter, dann ist der Spuk weg und auch AVR 
machts richtig.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ja, aber wie gesagt, in diesem Thread hat der TE ein ganz anderes
Problem.  Vielleicht reicht er uns ja mal ein wenig (compilierbaren)
Beispielcode nach.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Hat aber nichts mit einer globalen Variablen zu tun.

Sollte auch nur ein Beispiel dafür sein, daß der AVR-GCC manchmal recht 
eigensinnig ist.

Wenn man sich über Code ärgert, stellt man ihn um und hebt ihn selten 
auf. Daher hab ich kein Beispiel griffbereit. Es ist mir aber 
aufgefallen.

Mir ist auch mal das Gegenteil passiert, daß er 3 globale Variablen 
unnötig in Register geladen hat, obwohl ein switch/case nur je eine 
davon geändert hat. Dadurch gingen ihm aber Register verloren, so daß er 
für lokale Variablen extra einen Stackframe anlegen mußte. Auch da habe 
ich den fetten Code leider nicht aufgehoben.

Auch bei Arrayzugriffen auf Structs berechnet er gerne die Basisadresse 
immer wieder neu. Erst mit einem Hilfspointer kann man ihn überreden, 
direkt mit Displacement auf das Element zuzugreifen.
Im Scheduler Beispiel benutze ich das (-> Operator).
Ich würde mir aber wünschen, daß ein gut optimierender Compiler auch 
beim . Operator einen gleich kompakten Code erzeugt, d.h. von sich aus 
einen Basepointer anlegt.

Ich will mich hier aber nicht beklagen, für einen freien Compiler ist 
die Codedichte ja ausreichend.
Vom IAR würde ich aber mehr erwarten.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Das Dumme ist, dass man alle solche Fälle jeweils einzeln aufdröseln
muss (deshalb brauchen wir hier auch separate Threads dafür).  Johann
hat ja verdammt viel davon in den letzten Jahren schon abgeklappert und
bessere Lösungen eingebaut.

Mal sehen, ob sich der TE nun nochmal meldet.

von Kurt Schluss (Gast)


Lesenswert?

Konkretes Beispiel.
Ich brauche zur Kontrolle im Programm eine ganz simple Checksumme über 
den Flash. Die soll in einer globalen Variable stehen und wird einfach 
beim Start generiert.

1
Checksum = 0;
2
uint16_t j;
3
for(j = 0; j < (uint16_t)&__data_load_end; j++) Checksum += pgm_read_byte(j);
4
if (boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS) != 0b11110111) Checksum += 1;
5
if (boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS) != 0b11011100) Checksum += 2;
6
if (boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS) != 0b11111101) Checksum += 4;
7
if (boot_lock_fuse_bits_get(GET_LOCK_BITS) != 0b11101100) Checksum += 8;

Wenn ich es so schreibe, wird es kleiner:
1
uint8_t chk = 0;
2
uint16_t j;
3
for(j = 0; j < (uint16_t)&__data_load_end; j++) chk += pgm_read_byte(j);
4
if (boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS) != 0b11110111) chk += 1;
5
if (boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS) != 0b11011100) chk += 2;
6
if (boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS) != 0b11111101) chk += 4;
7
if (boot_lock_fuse_bits_get(GET_LOCK_BITS) != 0b11101100) chk += 8;
8
Checksum = chk;

Das ist, wie gesagt, ein Beispiel. Das Phänomen tritt fast immer auf. 
Sogar mit statischen Variablen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> Konkretes Beispiel.

Bitte compilierbar.

von Karl H. (kbuchegg)


Lesenswert?

Jörg W. schrieb:
> Kurt Schluss schrieb:
>> Konkretes Beispiel.
>
> Bitte compilierbar.

... zum Beispiel wegen der konkreten Datentypen von Variablen. Die sind 
immer wichtig, wenn es um Optimierungsprobleme geht.

von asdfasd (Gast)


Lesenswert?

> Konkretes Beispiel. [...]

Das liegt daran, dass zwischen den Zugriffen auf die globale Variable 
andere Funktionen aufgerufen werden.  Diese könnten den Inhalt der 
globalen Variable ändern, deshalb muss der Compiler den Inhalt jedesmal 
neu lesen.  Bei lokalen Variablen weiss der Compiler, dass 
Unterfunktionen die lokale Variable nicht ändern können und er kann die 
Zugriffe schön optimieren.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

asdfasd schrieb:
> Das liegt daran, dass zwischen den Zugriffen auf die globale Variable
> andere Funktionen aufgerufen werden.

Eigentlich nicht, die Dinger expandieren zu inline asm.

Deshalb ist es ja wichtig, dass man etwas Compilierbares hier hat,
damit man das auseinander klamüsern kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Z.B. hier sind die 2 RJMP überflüssig:

Hauptproblem sind Kostenbeschreibung im avr-gcc.  Dein Beispiel erzeugt 
mit -mbranch-cost=2 kleineren Code.  Allerdings erhalte ich mit dem 
Default (0) durchaus kleineren Code; im letzten Projekt sind das rund 2% 
(bei ca. 14k Binärcode).

Die Kosten zu überarbeiten wäre sehr zeitaufwändig und wird dadurch 
erschwert, dass es keine brauchbaren Benchmarks gibt.  Momentan haben 
Konstanten Kosten 0, was noch Denis stammt:

http://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr.c?revision=230016&view=markup#l9902

A. K. schrieb:
> Wer mal sehen will, auf welche skurrilen Lösungen Compiler kommen
> können, der sollte sich mal das Resultat für amd64 ansehen:

Find ich naheliegend und nicht skurril :-)  Ein Flag wird zu 0 resp. -1 
gewandelt und dies als Maske, Summand, o.ä. verwendet.  avr-gcc macht 
das z.B. bei
1
#include <stdfix.h>
2
3
fract addss (fract a, sat fract b)
4
{
5
    return a - b;
6
}
1
addss:
2
  sub  r24,r22
3
  sbc  r25,r23
4
  brvc 0f
5
  ldi  r25,0x7f
6
  cp   r25,r23
7
  sbc  r24,r24
8
  sbci r25,-1
9
0:
10
  ret

A. K. schrieb:
> Ist auch nicht wirklich optimal, auf Basis von CMOV gehts besser.

IIRC hatte ich damit mal rumgespielt, aber das wurde dann ein Fass ohne 
Boden...  Aber nett wär's schon, z.B für Rx unsigned:
1
Ry = Rx < const1 ? -1 : const2;
1
cpi  Rx, const1
2
sbc  Ry, Ry
3
ori  Ry, const2
===
1
Ry = Rx < const1 ? const2 : 0;
1
cpi  Rx, const1
2
sbc  Ry, Ry
3
andi Ry, const2
===
1
if (Rx < const1)
2
    Ry++;
1
cpi  Rx, const1
2
adc  Ry, __zero_reg__
===
1
if (Rx >= const1)
2
    Ry++;
1
cpi  Rx, const1
2
sbci Ry, -1

Wobei anstatt const sinngemäß auch ein Register stehen kann.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Find ich naheliegend und nicht skurril :-)

Du meinst, Asm-Experten wie Mobi verwenden routinemässig solchen Code?

Direkt selbsterkärend ist er jedenfalls nicht. Aus solchem Code 
rückwärts die eigentliche Operation rauszuporkeln ist Schwerstarbeit.

Vor langer Zeit bin ich mal dem GNU Superoptimizer (oder so ähnlich) 
begegnet, der alle Befehlssequenzen nacheinander durchzuprobieren 
scheint, um die Beste für eine kurze Operation zu finden.

: Bearbeitet durch User
von Kurt Schluss (Gast)


Angehängte Dateien:

Lesenswert?

So. Hat etwas gedauert, aber ich habe mal ein Minimalbeispiel 
zusammengebastelt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Danke!

Ja, es sind in der Tat die Inline-Assembler-Aufrufe, die offensichtlich
auf globale Variable ein Reload erzwingen.  Möglicherweise müssen die
zwar nicht "asm volatile" sein (wie sie es derzeit sind), aber auch
ohne das "volatile" ergibt sich der gleiche Code.  Ich hätte erwartet,
dass der Inline Assembler nur dann das Reload triggert, wenn man
explizit "memory" in der Clobber-Liste angibt.

Vielleicht hat Johann ja eine Idee.

p.s.: Habe auch mal das Umschreiben in eine static inline-Funktion
ausprobiert, die mit den Attributen "const" und "pure" deklariert
ist.  Hat leider auch keine Veränderung gebracht.

: Bearbeitet durch Moderator
von Kurt Schluss (Gast)


Lesenswert?

Mich würde das wirklich freuen, weil es sich in größeren Projekten schon 
läppert, was da an Codeverhau zusammenkommt. Alleine das kurze Stück 
macht schon 30 Bytes aus. Das Ganze x-mal, da kommt so einiges zusammen!
Das Reinbasteln von temporären Variablen, teilweise mehreren parallel 
ist halt sehr unschön. Und das Ganze passiert im Grunde unter allen 
gcc-Version, die ich getestet habe inkl. der 5er.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> Mich würde das wirklich freuen, weil es sich in größeren Projekten schon
> läppert, was da an Codeverhau zusammenkommt. Alleine das kurze Stück
> macht schon 30 Bytes aus.

Naja, aber siehe oben: das nächste Problem ist wieder ein ganz
anderes.  Diese Analyse trifft nur auf das soeben gepostete zu,
bei dem die Fuses gelesen werden sollen, denn das ist am Ende in
Inline Assembler implementiert.

Generell ist die Variante mit der lokalen "Cache"-Variablen natürlich
immer sinnvoll, denn die zeigt dem Compiler recht eindeutig an, dass es
keine globalen Seiteneffekte geben kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Habe auch mal das Umschreiben in eine static inline-Funktion
> ausprobiert, die mit den Attributen "const" und "pure" deklariert
> ist.

Eine Funktion, die geinlint wird, als const oder pure zu deklarieren, 
ist m.E. ziemlich sinnfrei.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Eine Funktion, die geinlint wird, als const oder pure zu deklarieren,
> ist m.E. ziemlich sinnfrei.

OK, überzeugt. :)  War nur 'ne Verzweiflungstat.

Hast du eine Idee, warum das asm-Statement auch ohne "memory"-Clobber
immer ein Reload erzwingt?

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Angehängte Dateien:

Lesenswert?

Hier das Ganze nochmal maximal "self-contained" (bis auf zwei
avr-libc-Header).  Kann man mit -DGLOBAL compilieren oder eben ohne.

von Kurt Schluss (Gast)


Lesenswert?

Das ist nur ein Beispiel. Dass da die Fuses gelesen werden, ist dem 
Zufall geschuldet. Der Effekt tritt, wie gesagt, auch an vielen anderen 
Stellen auf, wo mit globalen Variablen gerechnet wird.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ja, andere Ursachen wurden dir ja schon genannt: für eine externe
Funktion kann der Compiler (ohne weitere Deklarationen wie eben die
genannten Funktions-Attribute) nicht wissen, ob diese eine globale
Variable verändern oder nicht.  Das ist einer der Nachteile der Sprache
C.  Daher ist er dann gezwungen, den Fall anzunehmen, die aufgerufene
Funktion hätte ihn geändert, und muss den Wert neu laden.  (Peter,
probier's mit dem IAR, würde mich wundern, wenn das dort grundlegend
anders wäre.)

Das Cachen in einer lokalen Variablen ist dagegen immer eine Abhilfe,
denn dann ist für den Compiler völlig klar, dass die gerufene Funktion
deren Wert nicht geändert haben kann.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Hast du eine Idee, warum das asm-Statement auch ohne "memory"-Clobber
> immer ein Reload erzwingt?

Kann ich so nicht nachvollziehen.  Erst wenn in folgendem Code der 
Vergleich einkommentiert wird, gibt es zusätzliche globale Zugriffe
1
#include <avr/io.h>
2
3
static inline uint8_t f (void)
4
{
5
    uint8_t x;
6
    __asm __volatile__ ("; =%0,%=" : "=r" (x));
7
    return x;
8
}
9
10
extern uint8_t X;
11
12
void X_init (void)
13
{
14
    X = 0;
15
    X += f();
16
    //if (X != 10)
17
        X -= f();
18
    X *= f ();
19
}

Unabhängig davon finde ich eine getChecksum() besser als eine 
initChecksum().

von Dieter F. (Gast)


Lesenswert?

Blah Blubb
und wieder gelöscht

Peter D. schrieb:
> Jörg W. schrieb:
>> Naja, das solltest du schon näher erläutern.
>
> Z.B. hier sind die 2 RJMP überflüssig:void foo( uint8_t val )
> {
>   uint8_t i = 45;
>   if( val & 0x08 )
>   44:  83 ff         sbrs  r24, 3
>   46:  02 c0         rjmp  .+4        ; 0x4c <foo+0x8>
>   48:  8b e7         ldi  r24, 0x7B  ; 123
>   4a:  01 c0         rjmp  .+2        ; 0x4e <foo+0xa>
>   4c:  8d e2         ldi  r24, 0x2D  ; 45
>     i = 123;
>   PORTB = i;
>   4e:  88 bb         out  0x18, r24  ; 24
> }
>   50:  08 95         ret

Woher weiß PeDa das? Ist bisher nicht veröffentlicht. Nur für Dich, 
Jörg!

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dieter F. schrieb:
> Woher weiß PeDa das?

Dieter, bitte bleib mal bei dem Code des TE, über den wir zuletzt
diskutiert haben.  Das von PeDa war abgehakt bzw. gehört, wenn schon,
in einen eigenen Thread.

Das Problem des TEs ist ein anderes, aber dafür muss man erstmal den
ganzen Thread lesen.

von Dieter F. (Gast)


Lesenswert?

Jörg W. schrieb:
> Dieter, bitte bleib mal bei dem Code des TE,

Gerne, der war nur bis dahin 
Beitrag "Re: Codeblähungen beim Rechnen mit globaler Variable" nicht 
bekannt.

Und deswegen einfach Beiträge zu löschen ist nicht gerade fein.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Dieter F. schrieb:

> Gerne, der war nur bis dahin
> Beitrag "Re: Codeblähungen beim Rechnen mit globaler Variable" nicht
> bekannt.

Das ist richtig, aber seit 15:51 Uhr ist er es, und seitdem haben wir
genau darüber diskutiert.

Der Rest gehört nicht hierher, den habe ich dir in einer Mail erläutert.

von Dieter F. (Gast)


Lesenswert?

Jörg W. schrieb:
> as ist richtig, aber seit 15:51 Uhr ist er es, und seitdem haben wir
> genau darüber diskutiert.

Nein, diskutiert wurde vorher schon (für einige) - und ich finde es 
nicht richtig, das andere ausgeschlossen wurden.

Ist aber egal, ich werde das bei Andreas vorbringen und die Antwort 
abwarten. Wenn er das für korrekt hält bin ich halt raus ...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
>> Hast du eine Idee, warum das asm-Statement auch ohne "memory"-Clobber
>> immer ein Reload erzwingt?
>
> Kann ich so nicht nachvollziehen.

Schade auch.  Ich hatte ein bisschen drauf gehofft, dass du dir auf
die diversen Zwischenschritte, die der GCC im Debugmodus ausgibt,
einen Reim machen könntest.

Als Fazit für den TE bleibt meiner Meinung nach, dass es immer sinnvoll
ist, sowas in lokalen Variablen zwischenzuspeichern.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg W. schrieb:
> Johann L. schrieb:
>>> Hast du eine Idee, warum das asm-Statement auch ohne "memory"-Clobber
>>> immer ein Reload erzwingt?
>>
>> Kann ich so nicht nachvollziehen.
>
> Schade auch.  Ich hatte ein bisschen drauf gehofft, dass du dir auf
> die diversen Zwischenschritte, die der GCC im Debugmodus ausgibt,
> einen Reim machen könntest.

Momentan hab ich keinen Nerv, darin rumzuwühlen.  Jedenfalls hats's nix 
mit asm und nix mit den volatiles und nix mit Aliasing zu tun, 
vergleiche den Code per
1
#include <avr/boot.h>
2
3
extern uint8_t Checksum;
4
5
void checksum_init2 (void)
6
{
7
  Checksum = 0;
8
  Checksum += 1 * (boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS) != 0b11110111);
9
  Checksum += 2 * (boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS) != 0b11011100);
10
  Checksum += 4 * (boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS) != 0b11111101);
11
  Checksum += 8 * (boot_lock_fuse_bits_get(GET_LOCK_BITS) != 0b11101100);
12
}

Wahrscheinlich irgendwo in PHI, PRE, FRE, CSE (nebst Target-Kosten) den 
Target-unabhängigen Passes.  Mit den Beispielen sollte zumindest der 
Pass, wo die Optimierung erwartet wird, auffindbar sein.

Z.B. per -fdump-tree-all-details

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


Lesenswert?

...z.B. indem man bei .optimized anfängt und dann für init2 rausfindet, 
welcher Pass SSA-Names wie Checksum.19_31 einführt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Momentan hab ich keinen Nerv, darin rumzuwühlen.

OK, versteh' ich.  Trotzdem danke erstmal. ;)

von Kurt Schluss (Gast)


Lesenswert?

Hier nochmal ein weiteres Beispiel
1
#include <avr/io.h>
2
3
uint8_t Flags;
4
5
int __attribute__((OS_main)) main(void) {
6
  int16_t value = GPIOR1;
7
8
  #if 0
9
10
  static uint8_t A = 0;
11
  uint8_t tempA = A;
12
  if (value > 10) {
13
    if (tempA > 7) Flags |= 1;
14
    else tempA++;
15
  } else if (value < 5) {
16
    Flags &= ~1;
17
    tempA = 0;
18
  }
19
  A = tempA;
20
21
  #else
22
23
  static uint8_t A = 0;
24
  if (value > 10) {
25
    if (A > 7) Flags |= 1;
26
    else A++;
27
  } else if (value < 5) {
28
    Flags &= ~1;
29
    A = 0;
30
  }
31
32
  #endif
33
34
  GPIOR0 = Flags;
35
36
  while (1);
37
}

Ist direkt kompilierbar und ihr könnt #if 0 einfach durch #if 1 
ersetzen, um den (mir unverständlichen) Unterschied zu sehen.

von Sheeva P. (sheevaplug)


Lesenswert?

Kurt Schluss schrieb:
> ich verwende avr-gcc zur Kompilierung für einen Atmega.
>
> Ich habe jetzt schon mehrfach beobachtet, dass es den Code aufbläht,
> wenn man mit globalen Variablen rechnet, anstatt mit temporären, lokalen
> Variablen, die man dann am Ende der globalen Variable zuweist.
>
> Warum ist das so? Sollte der Compiler nicht eigentlich sowas automatisch
> optimieren? Ich finde, es macht die Sache auch immer etwas
> unübersichtlich.
> Wenn die globale Variable volatile wäre, wäre es das ja einleuchtend.
> Ist sie aber in dem Fall gar nicht mal.

Globale Variablen sind igitt. Darum haben sich die Compilerentwickler 
keine Mühe gegeben, den Compiler für solchen Quark zu verbessern. Die 
haben genug damit zu tun, Optimierungen für guten Code zu entwickeln. 
;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sheeva P. schrieb:
> Darum haben sich die Compilerentwickler keine Mühe gegeben, den Compiler
> für solchen Quark zu verbessern.

Nö, das Problem dabei ist nur, dass man gemäß den Regeln von C sich
eben damit in vielen Fällen gar keine Mühe geben kann.

(Das letzte Beispiel habe ich mir aber noch nicht angesehen.)

von Kurt Schluss (Gast)


Lesenswert?

Das neue Beispiel zeigt das Problem mit static Variablen. Sind also 
nicht nur die "bösen" globalen Variablen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> ind also nicht nur die "bösen" globalen Variablen.

Und was ist "Flags"?

von Kurt Schluss (Gast)


Lesenswert?

Flags ist eine globale Variable, die dieses Mal nicht in einer 
temporären Variable zwischengespeichert wird! Mir ging es diesmal nur 
darum, zu zeigen, dass das selbe Verhalten auch bei static auftritt.

Aaaber natürlich könnte man auch Flags zwischenspeichern! Kannst es ja 
mal probieren. Bringt auch wieder einige Bytes. Leider wird der Code 
dann langsam aber sicher unleserlich... und genau das ist, was mir nicht 
gefällt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> Mir ging es diesmal nur darum, zu zeigen, dass das selbe Verhalten auch
> bei static auftritt.

static ist nicht das Thema, sondern Flags.

von Kurt Schluss (Gast)


Lesenswert?

Dann hast du dir das neue Beispiel nicht gründlich genug angeschaut.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> Dann hast du dir das neue Beispiel nicht gründlich genug angeschaut.

Ja, ist auch schon spät genug.  Aber allzu groß sind die Unterschiede
auch nicht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> Ist direkt kompilierbar und ihr könnt #if 0 einfach durch #if 1
> ersetzen, um den (mir unverständlichen) Unterschied zu sehen.

Hab ich mal gemacht - mit avr-gcc 4.7.2.

Ergebnis: Die Variante mit(!) temporärer Variable ist länger.

Mit temporärer Variable (#if 1):
1
avr-gcc  -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99 -da -flto     -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT test.o -MF dep/test.o.d  -c  ../test.c
2
avr-gcc -mmcu=atmega328p -flto  -Os -Wl,-Map=test.map test.o     -o test.elf
3
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature  test.elf test.hex
4
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex test.elf test.eep || exit 0
5
avr-objdump -h -S test.elf > test.lss
6
7
AVR Memory Usage
8
----------------
9
Device: atmega328p
10
11
Program:     206 bytes (0.6% Full)
12
(.text + .data + .bootloader)
13
14
Data:          2 bytes (0.1% Full)
15
(.data + .bss + .noinit)
16
17
Build succeeded with 0 Warnings...

Ohne temporäre Variable (#if 0):
1
avr-gcc  -mmcu=atmega328p -Wall -gdwarf-2 -std=gnu99 -da -flto     -DF_CPU=8000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT test.o -MF dep/test.o.d  -c  ../test.c
2
avr-gcc -mmcu=atmega328p -flto  -Os -Wl,-Map=test.map test.o     -o test.elf
3
avr-objcopy -O ihex -R .eeprom -R .fuse -R .lock -R .signature  test.elf test.hex
4
avr-objcopy -j .eeprom --set-section-flags=.eeprom="alloc,load" --change-section-lma .eeprom=0 --no-change-warnings -O ihex test.elf test.eep || exit 0
5
avr-objdump -h -S test.elf > test.lss
6
7
AVR Memory Usage
8
----------------
9
Device: atmega328p
10
11
Program:     196 bytes (0.6% Full)
12
(.text + .data + .bootloader)
13
14
Data:          2 bytes (0.1% Full)
15
(.data + .bss + .noinit)
16
17
Build succeeded with 0 Warnings...

Also wo ist das Problem? Oder anders gefragt: welche avr-gcc-Version 
verwendest Du?

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

GCC 4.9.3:
1
$ diff -u notempvar.s  tempvar.s
2
--- notempvar.s 2015-12-01 11:44:04.342113974 +0100
3
+++ tempvar.s   2015-12-01 11:43:45.117960315 +0100
4
@@ -14,23 +14,28 @@
5
 .L__stack_usage = 0
6
        in r18,0x2a
7
        ldi r19,0
8
+       lds r25,A.1518
9
        lds r24,Flags
10
        cpi r18,11
11
        cpc r19,__zero_reg__
12
        brlt .L2
13
-       lds r25,A.1518
14
        cpi r25,lo8(8)
15
-       brlo .L4
16
+       brlo .L3
17
        ori r24,lo8(1)
18
-       rjmp .L6
19
+       sts Flags,r24
20
+       rjmp .L4
21
+.L3:
22
+       subi r25,lo8(-(1))
23
+       rjmp .L4
24
 .L2:
25
        cpi r18,5
26
        cpc r19,__zero_reg__
27
        brge .L4
28
        andi r24,lo8(-2)
29
-.L6:
30
        sts Flags,r24
31
+       ldi r25,0
32
 .L4:
33
+       sts A.1518,r25
34
        lds r24,Flags
35
        out 0x1e,r24
36
 .L5:

von Kurt Schluss (Gast)


Lesenswert?

Hmmmmmmm. Hatte es auch mit der 4.9.3 getestet, bzw. auch damit zum 
Minimalbeispiel reduziert. Dass es bei anderen Versionen gegensätzliche 
Ergebnisse gibt, ist jetzt nicht gerade vertrauenserweckend. :/

Ich sehe aber gerade, dass mein Beispiel auch zum Teil Käse ist: Der 
Zähler A kann bei nur einem Durchlauf (wie im reduzierten 
Minimalbeispiel) nie über 1 hinaus kommen. Da ist jetzt die Frage, wie 
schlau der Compiler ist und was er wegoptimiert. Normal müsste man hier 
alles in ein while (1) {...} packen.

Im realen Programm macht es jedenfalls so einige Bytes aus, mit vs. ohne 
tempA.

von (prx) A. K. (prx)


Lesenswert?

Kurt Schluss schrieb:
> Dass es bei anderen Versionen gegensätzliche
> Ergebnisse gibt, ist jetzt nicht gerade vertrauenserweckend. :/

Aber auch nicht weiter ungewöhnlich. Der Optimizer des GCC besteht aus 
vielen Teilen und die meisten sind nicht speziell auf eine Zielmaschine 
abgestimmt. Die werden deshalb auch von etlichen verschiedenen Leuten 
gepflegt. Es kann folglich vorkommen, dass Änderungen an im Prinzip 
maschinenunabhängigen Optimierungen bei manchen Zielmaschinen nach 
hinten losgehen.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Kurt Schluss schrieb:
> Hmmmmmmm. Hatte es auch mit der 4.9.3 getestet, bzw. auch damit zum
> Minimalbeispiel reduziert. Dass es bei anderen Versionen gegensätzliche
> Ergebnisse gibt, ist jetzt nicht gerade vertrauenserweckend. :/

Warum? Da gibt es noch diverse Knöpfchen. Fängt bei der Optimierung an:

   -O, -O2, -O3, -Os

> Im realen Programm macht es jedenfalls so einige Bytes aus, mit vs. ohne
> tempA.

Bei statics, die lokal in der Funktion definiert werden? Glaube ich 
nicht.

von Kurt Schluss (Gast)


Lesenswert?

Probiere es bitte selbst mal aus. Du hast doch bestimmt ein größeres 
Projekt mit static Variablen zur Verfügung.

von Heimleiter (Gast)


Lesenswert?

A. K. schrieb:
> Wer mal sehen will, auf welche skurrilen Lösungen Compiler kommen
> können, der sollte sich mal das Resultat für amd64 ansehen:
>         andl    $8, %edi
>         cmpb    $1, %dil
>         sbbl    %eax, %eax
>         andl    $-78, %eax
>         addl    $123, %eax
>         movb    %al, PORTB(%rip)

nennt sich branchless condition, guter code

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.