www.mikrocontroller.net

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


Autor: Andreas Lemke (Firma: Systementwicklung A. Lemke) (andreas79)
Datum:

Bewertung
0 lesenswert
nicht 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:
volatile char fifoBuffer[FIFO_LENGTH];
wobei FIFO_LENGTH = 64 ist und die Funktion:
char getFifo(){
  static char pReadOffset = 0;
  char nextChar = *(fifoBuffer + pReadOffset * sizeof(char*));
  *(fifoBuffer + pReadOffset * sizeof(char*)) = 0x00;

  if((fifoBuffer + pReadOffset * sizeof(char*)) - fifoBuffer == (FIFO_LENGTH - 1) ){
    pReadOffset = 0;
  }else{
    pReadOffset++;
  }
  return nextChar;
}
In der Hoffnung ein paar Bytes einsparen zu können habe ich die Funktion 
wie folgt abgeändert:
char getFifo(){
  static char pReadOffset = 0;
  char nextChar = *(fifoBuffer + pReadOffset);
  *(fifoBuffer + pReadOffset) = 0x00;

  if((fifoBuffer + pReadOffset) - fifoBuffer == (FIFO_LENGTH - 1) ){
    pReadOffset = 0;
  }else{
    pReadOffset += sizeof(char*);
  }
  return nextChar;
}
also die 3 Multiplikationen mit
sizeof(char*)
 durch diese eine
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:
char nextChar = *(fifoBuffer + pReadOffset * sizeof(char*));

Wenn ich dort den Ausdruck
* 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

char getFifo(){
  static char pReadOffset = 0;
  char nextChar = fifoBuffer[ pReadOffset ];
  fifoBuffer[ pReadOffset ] = 0x00;

  if( pReadOffset == (FIFO_LENGTH - 1) ){
    pReadOffset = 0;
  }else{
    pReadOffset++;
  }
  return nextChar;
}

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.

Autor: Andreas Lemke (Firma: Systementwicklung A. Lemke) (andreas79)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.
char getFifo(){
  static char* pReadOffset = fifoBuffer;
  char nextChar = *pReadOffset;
  *pReadOffset = 0x00;

  if( pReadOffset == fifoBuffer + FIFO_LENGTH - 1 ){
    pReadOffset = fifoBuffer;
  }else{
    pReadOffset++;
  }
  return nextChar;
}

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.

Autor: Andreas Lemke (Firma: Systementwicklung A. Lemke) (andreas79)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Beim AVR ist meist -Os sinnvoll, -O2 ist fast immer Unsinn.

Autor: der mechatroniker (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Seit wann inlinet O2? Ich dachte das macht nur O3. Wieder was 
dazugelernt.

Autor: Andreas Lemke (Firma: Systementwicklung A. Lemke) (andreas79)
Datum:

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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 ...

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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. 
;-)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.