Forum: Compiler & IDEs Compiler bläht Code nach entfernter Anweisung auf.


von Andreas L. (Firma: Systementwicklung A. Lemke) (andreas79)


Lesenswert?

Hallo an die Runde,

ich habe folgendes Problem mit meinem avr-gcc:
In einem Programm für einen ATMega8 ist die folgende Funktion enthalten, 
welche ein Zeichen aus einem Fifo Ringpuffer zurückliefert. Dazu ist 
gobal definiert:
1
volatile char fifoBuffer[FIFO_LENGTH];
wobei FIFO_LENGTH = 64 ist und die Funktion:
1
char getFifo(){
2
  static char pReadOffset = 0;
3
  char nextChar = *(fifoBuffer + pReadOffset * sizeof(char*));
4
  *(fifoBuffer + pReadOffset * sizeof(char*)) = 0x00;
5
6
  if((fifoBuffer + pReadOffset * sizeof(char*)) - fifoBuffer == (FIFO_LENGTH - 1) ){
7
    pReadOffset = 0;
8
  }else{
9
    pReadOffset++;
10
  }
11
  return nextChar;
12
}
In der Hoffnung ein paar Bytes einsparen zu können habe ich die Funktion 
wie folgt abgeändert:
1
char getFifo(){
2
  static char pReadOffset = 0;
3
  char nextChar = *(fifoBuffer + pReadOffset);
4
  *(fifoBuffer + pReadOffset) = 0x00;
5
6
  if((fifoBuffer + pReadOffset) - fifoBuffer == (FIFO_LENGTH - 1) ){
7
    pReadOffset = 0;
8
  }else{
9
    pReadOffset += sizeof(char*);
10
  }
11
  return nextChar;
12
}
also die 3 Multiplikationen mit
1
sizeof(char*)
 durch diese eine
1
pReadOffset += sizeof(char*);
 ersetzt. Im Ergebnis ist die Hex-Datei des Programms nun 3519 Byte 
groß, vor der Änderung betrug die Größe 2820 Byte. Der Effekt ist 
reproduzierbar, Compilereinstellungen wurden nicht verändert.

Ich konnte die Ursache auf folgende Codezeile eingrenzen:
1
char nextChar = *(fifoBuffer + pReadOffset * sizeof(char*));

Wenn ich dort den Ausdruck
1
* sizeof(char*)
 entferne, wächst die Programmgröße von 2820 auf 3519 Byte.

Vielleicht kennt ja jemand die Lösung des Rätsels und kann mit auf die 
Sprünge helfen.

von Karl H. (kbuchegg)


Lesenswert?

Was um alles in der Welt speicherst du in deiner Fifo, dass da überall 
char* auftaucht?

Schreib doch den Code so, wie er für dich am einfachsten ist, nämlich 
zuerst einmal korrekt! In deiner Jagd nach kürzerem Code hast du dich 
doch hinten und vorne selbst ausgetrickst.

1
char getFifo(){
2
  static char pReadOffset = 0;
3
  char nextChar = fifoBuffer[ pReadOffset ];
4
  fifoBuffer[ pReadOffset ] = 0x00;
5
6
  if( pReadOffset == (FIFO_LENGTH - 1) ){
7
    pReadOffset = 0;
8
  }else{
9
    pReadOffset++;
10
  }
11
  return nextChar;
12
}

und alles Weitere überlässt du dem Optimizer des Compilers.

Es kann schon sein, dass der Code insgesamt dadurch länger wird. Nämlich 
dann wenn die Funktionsgröße unter die Grenze fällt und der Compiler 
anfängt die Funktion inline an den Aufrufstellen einzusetzen.

von Andreas L. (Firma: Systementwicklung A. Lemke) (andreas79)


Lesenswert?

Hallo Karl Heinz,

danke für Deine Antwort. Die von Dir beschriebene Variante über eine 
Index auf die Fifo zuzugreifen, hatte ich anfänglich realisiert, sie 
dann aber verworfen, da die hier gezeigte Version - mit einem Offset auf 
dem (dereferenzierten) Pointer - ein kleineres Hex-File erzeugt.

Da es sich um ein char Array handelt, liegen dessen Einträge um genau 
sizeof(char*) versetzt im Speicher, daher die Anwendung.
>Was um alles in der Welt speicherst du in deiner Fifo, dass da überall
>char* auftaucht?
In meiner "reklamierten" Funktion ist übrigens nur 1 x sizeof(char*) 
enthalten.

Grüße

Andreas

von Karl H. (kbuchegg)


Lesenswert?

Andreas Lemke schrieb:
> Hallo Karl Heinz,
>
> danke für Deine Antwort. Die von Dir beschriebene Variante über eine
> Index auf die Fifo zuzugreifen, hatte ich anfänglich realisiert, sie
> dann aber verworfen, da die hier gezeigte Version - mit einem Offset auf
> dem (dereferenzierten) Pointer - ein kleineres Hex-File erzeugt.

Das glaub ich dir nicht.

Die C-regeln verlangen nämlich vom Compiler, dass

   a[i]

identisch behandelt wird, wie

   *( a + i )

und da der Compiler das auch weiss, ist das erste was er intern mit

  char nextChar = fifoBuffer[ pReadOffset ];

macht, es für sich umzuwandeln in

  char nextChar = *( fifoBuffer + pReadOffset );

Und das ist gut so. Denn genau das MUSS der Compiler auch machen. Sorry, 
aber so sind nun mal die C-Regeln und der Compiler hat gar keine andere 
Wahl.

> Da es sich um ein char Array handelt, liegen dessen Einträge um genau
> sizeof(char*) versetzt im Speicher,

Nein, das tun sie nicht.
Da es ein char Array ist, liegen sie um sizeof(char) auseinander. Aber 
darum brauchst du dich nicht kümmern. Denn auch das regeln die C-Regeln, 
konkret die Regeln welche Pointerarithmetik betreffen. Und über den 
vorher besprochenen Zusammenhang zwischen Pointer Arithmetik und 
Index-Operation ist damit auch automatisch die korrekte Array-Index 
Opertion definiert. Das beste was du tun kannst, ist daher die 
Indexoperation zu verwenden und dein Compiler erzeugt dafür automatisch 
optimalen Code. Es gibt nichts was du tun kannst, um

  char nextChar = fifoBuffer[ pReadOffset ];

in einer anderen Variante zu schreiben, die schneller ist.
Die einzige Ausnahme besteht darin, wenn derartige Indexoperationen in 
einer Schleife auftauchen und der Index zb. regelmässig anwächst.

   for( i = 0; i < irgendwas; ++i )
     a[i] = ...

dann könnte ein Austausch der Indexoperation durch Pointerarithmetik 
Vorteile bringen.

   for( pTemp = a; pTemp < a + irgendwas; ++pTemp )
     *pTemp = ...

Könnte deswegen, weil die meisten Compiler so ein Konstrukt erkennen und 
den Austausch selbsttätig machen.


> da die hier gezeigte Version - mit einem Offset auf
> dem (dereferenzierten) Pointer - ein kleineres Hex-File erzeugt.

Was wahrscheinlich daher rührt, dass die Funktion insgesamt zu lange 
geworden ist und sie der Compiler nicht mehr geinlined hat.

Das ändert aber nichts daran, dass du bei deiner Transformation die 
Funktionalität der Funktion verschlimmbessert hast. Sprich: du hast 
Fehler eingebaut.

von Karl H. (kbuchegg)


Lesenswert?

Man kann allerdings tatsächlich die komplette Operation etwas 
beschleunigen, indem man sich nicht den Index des nächsten zu liefernden 
Elements merkt, sondern gleich einen Pointer darauf. Dann muss beim 
Zugriff nämlich überhaupt nicht mehr gerechnet werden.
1
char getFifo(){
2
  static char* pReadOffset = fifoBuffer;
3
  char nextChar = *pReadOffset;
4
  *pReadOffset = 0x00;
5
6
  if( pReadOffset == fifoBuffer + FIFO_LENGTH - 1 ){
7
    pReadOffset = fifoBuffer;
8
  }else{
9
    pReadOffset++;
10
  }
11
  return nextChar;
12
}

Das ist eine Anwendung der alten Regel: Time for Space
Indem man ein klein wenig Speicher investiert (ein Pointer verbraucht 
mehr Speicher als ein einzelner char), kann man sich ein wenig Laufzeit 
erkaufen, indem man sich ein besser geeignetes 'temporäres Ergebnis' 
abspeichert. In diesem Fall die Adresse des nächsten Elements anstelle 
seines Indexes.

von Andreas L. (Firma: Systementwicklung A. Lemke) (andreas79)


Lesenswert?

Hallo Karl Heinz,

danke für Deine Erläuterungen. Das Zauberwort scheint tatsächlich das 
automatische Inline durch den Compiler zu sein. Wenn ich das 
Optimization Level von -O2 auf -O1 ändere, schrumpft der resultierende 
Code um etwa 400 Byte, was in etwa mit der Zahl der Funktionsaufrufe im 
Programm korreliert.

Grüße

Andreas

von (prx) A. K. (prx)


Lesenswert?

Beim AVR ist meist -Os sinnvoll, -O2 ist fast immer Unsinn.

von der mechatroniker (Gast)


Lesenswert?

Seit wann inlinet O2? Ich dachte das macht nur O3. Wieder was 
dazugelernt.

von Andreas L. (Firma: Systementwicklung A. Lemke) (andreas79)


Lesenswert?

siehe http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html unter -O2:
...
-finline-small-functions
...

von Karl H. (kbuchegg)


Lesenswert?

Andreas Lemke schrieb:
> Hallo Karl Heinz,
>
> danke für Deine Erläuterungen. Das Zauberwort scheint tatsächlich das
> automatische Inline durch den Compiler zu sein. Wenn ich das
> Optimization Level von -O2 auf -O1 ändere, schrumpft der resultierende
> Code um etwa 400 Byte, was in etwa mit der Zahl der Funktionsaufrufe im
> Programm korreliert.

Wenn das zu einem Problem wird, ist es vernünftiger dem Compiler 
mitzuteilen, dass du diese Funktion nicht inlinen willst.

Das kannst du mit regulären C-Mitteln machen, indem du die Funktion in 
eine andere *.c Datei verbannst und so dem Compiler die Möglichkeit 
nimmst beim Aufruf den Inhalt der Funktion zu sehen sodass er technisch 
gar nicht mehr inlinen kann.

Oder aber du verpasst der Funktion ein noinline Attribute
char getFifo() _attribute_ ((noinline))


Wenn du aber genug Platz hast, so dass es keine Rolle spielt ob inline 
oder nicht, dann lass dem Optimizer die Freude. Für nicht benutztes 
Flash gibts von Atmel kein Geld zurück :-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Warum wählst du -O2 und beschwerst dich dann über breiten Code? O2 
Optimiert auf Geschwindigkeit, nicht auf Codegröße.

Also einfach mal an den Optionen spielen oder mit -save-temps 
-fverbose-asm im .s schauen, welche Optionen O2 bzw. Os nach sich 
ziehen. Und dann zB

-Os -fno-inline-small-functions -fno-split-wide-types 
-fno-tree-scev-cprop ...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:

> Wenn du aber genug Platz hast, so dass es keine Rolle spielt ob inline
> oder nicht, dann lass dem Optimizer die Freude. Für nicht benutztes
> Flash gibts von Atmel kein Geld zurück :-)

Aber wenn man irgendwann mehr als 100% braucht, gibt's Knete für Atmel. 
;-)

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.