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:
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:
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
chargetFifo(){
2
staticcharpReadOffset=0;
3
charnextChar=fifoBuffer[pReadOffset];
4
fifoBuffer[pReadOffset]=0x00;
5
6
if(pReadOffset==(FIFO_LENGTH-1)){
7
pReadOffset=0;
8
}else{
9
pReadOffset++;
10
}
11
returnnextChar;
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.
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
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.
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
chargetFifo(){
2
staticchar*pReadOffset=fifoBuffer;
3
charnextChar=*pReadOffset;
4
*pReadOffset=0x00;
5
6
if(pReadOffset==fifoBuffer+FIFO_LENGTH-1){
7
pReadOffset=fifoBuffer;
8
}else{
9
pReadOffset++;
10
}
11
returnnextChar;
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.
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
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 :-)
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 ...
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.
;-)