Hallo,
ich habe ein kleines Problem. Bei einenm Projekt mit einem Mega8 habe
ich den Flash fast voll.
Aber zwischen den beiden Compilervarianten 20060421 <-> 20090313 erzeugt
der WinAvr von 2006 einen 500byte kleineren code als der aktuelle
WinAvr.
Die optimierung ist jeweils schon auf "s" eingestellt.
Aus dem Map file werde ich nicht richtig schlau, wie bekomme ich denn
eine Ausflistung aus dem COmpiler welche funktion wie viel speicher im
Controller belegt?
MfG
Sebastian
Sebastian____ wrote:
> Aus dem Map file werde ich nicht richtig schlau, wie bekomme ich denn> eine Ausflistung aus dem COmpiler welche funktion wie viel speicher im> Controller belegt?
Eben aus dem Mapfile. Ohne es zu sehen ist die Analyse davon aber noch
deutlich schwieriger ;-).
Sebastian____ wrote:
> Hallo,> ich habe ein kleines Problem. Bei einenm Projekt mit einem Mega8 habe> ich den Flash fast voll.> Aber zwischen den beiden Compilervarianten 20060421 <-> 20090313 erzeugt> der WinAvr von 2006 einen 500byte kleineren code als der aktuelle> WinAvr.
Probleme bereiten gerne eeprom_read_block und eeprom_wriet_block aus der
avr-libc, sie mit -mcall-prologues übersetzt sind.
Der Code bekommt man mit -fno-inline-functions idR kleiner, ob
-fno-split-wide-types etwas hilft, hängt von der konkreten Quelle ab.
Daß gcc 4.x größeren Code macht als 3.4.6 ist bekannt. Insbesondere
führen bei 4.x Loop-Unrolling/Peeling, If-Conversion und Loop-Invariant
Motion auch zu größerem Code.
Johann
Stefan B. wrote:
> Vielleicht hilft AVR-GCC-Codeoptimierung bzw. kann man den Artikel> für aktuelle AVR-GCC Versionen updaten (nur 3 Überarbeitungen 2008/2009> bisher).
Mit Verlaub: Was da steht ist in bezug auf foo-gcc ziemlicher Humbug.
Der Artikel basiert euf einer Application-Note von Atmel für den IAR.
Johann
Johann L. wrote:
> Mit Verlaub: Was da steht ist in bezug auf foo-gcc ziemlicher Humbug.> Der Artikel basiert euf einer Application-Note von Atmel für den IAR.
Und jetzt? Soll man den Artikel entfernen oder kann man ihn verbessern?
Oder möchtest du deine Äusserung belegen, darüber diskutieren oder
einfach nur als Statement so stehen lassen?
Stefan B. wrote:
> Johann L. wrote:>>> Mit Verlaub: Was da steht ist in bezug auf foo-gcc ziemlicher Humbug.>> Der Artikel basiert euf einer Application-Note von Atmel für den IAR.>> Und jetzt? Soll man den Artikel entfernen oder kann man ihn verbessern?> Oder möchtest du deine Äusserung belegen, darüber diskutieren oder> einfach nur als Statement so stehen lassen?
Das kannst du einfach belegen, indem du zum Beispiel dan am
Artikel-Anfang angegebenen Codeschnippsel gcc zum Fraß vorwirfst und
das Resultat mit dem angeblichen Resultat vergleichst. Du wirst
feststellen, daß gcc absolute Adressierung verwendet.
Mit -mint8 ist man wie gesagt nicht mehr interlink-fähig mit der libc
und der libm. Die libgcc sollte passen, allerdings muss einem klar sein,
daß es durch -mint8 massive Verschiebungen in der Semantik des
C-Programmes gibt. Man lasse sich zB mal sizeof (long int) zusammen mit
-mint8 anzeigen.
Ähnliche Fallstricke haben auch globale Registervariablen. Wenn man
nicht genau weiß, was das für Konsequenzen hat bzw. haben kann, fliegt
einem alles um die Ohren. Fieserweise nicht nach den Änderungen, sondern
ne Woche später nach ner Änderung an einer ganz anderen Codestelle. Und
dann sucht man sich den Wolf bis der Arzt kommt...
Natürlich können diese Techniken zu kleinem und schnellem Code
beitragen, aber globale Registervariablen non-chalant zu empfehlen,
finde ich schon unfair gegenüber jemand, der sich mit den Nebeneffekten
und Implikationen nicht genauestens auskennt, und dazu dürften die
meisten Leser der Seite gehören. Globale Registervariablen sind eben was
komplett anderes als globale Variablen!!
Und ganz am Rande: Auch bei -O0 führt gcc Optimierungen durch. Er faltet
zum Beispiel Konstanten. Beispiel: a=1+2 wird zu a=3. Und was das zu
"Nullinitialisierung" steht, muss man nicht wirklich verstehen...
Johann
Hagen Re wrote:
> @Johann: und warum änderst du dann nicht den Wiki Beitrag, wenn du schon> so sinnvolle Hinweise hast ?
Mit indirekter Adressierung lässt sich durchaus merklich Code sparen --
wenn man es richtig einzusetzen weiß. Dafür gibt es allerdings keine
allgemeinen Tipps, schon garnicht wenn man nur ein paar Quellzeilen hat
mit davor und dahinter nur Pünktchen, Pünktchen, Pünktchen.
Wenn es so einfach wäre, das zu beschreiben, dann wär es schön längst im
AVR-Teil von GCC, glaub mir.
Es hängt davon ab
-- wieviel Zugriffe von welcher Breite geschehen
-- ob dazwischen Funktionsaufrufe sind oder nicht
-- ob die Funktion eine ISR ist oder nicht
-- ob und wieviele andere indirekte Zugriffe geschehen (dazu gehört auch
LPM)
-- wie hoch die Registerlast ist
-- wie groß die auftretenden Offsets sind
-- etc.
Last not least: es bringt auch nix, ein Symbol in ein Register zu
forcieren, wenn gcc das Register dann zb nach R14 allokiert. Dann hat
man zwar das Symbol in einem Register und es werden indirekte Zugriffe
gemacht, aber vor jedem Zugriff wird der Zeiger in ein Pointer-Register
kopiert. Code hat man dann keinen gewonnen, dafür nur Zeit verloren.
Was gcc angeht, so sind die Kosten absolute Adressierung versus
indirekte Adressierung erst nach erfolgter Registerallokierung bekannt.
Aber danach sind die Instruktionen und Register so verwoben, daß die
Adressierungsart nicht mehr geändert werden kann. Vor der
Registerallokierung kennt man aber die Kosten noch nicht, weil den
Registern idR noch keinem Hardware-Register zugeordnet sind. Ich hätte
noch nichtmal ne Idee, wo man in gcc diese Optimierung einbauen könnte.
Expand (also direkt nach dem Übergang GIMPLE->RTL) wäre nett, ist aber
viel zu früh, weil man nix über die Kosten weiß. CSE (common
subextression elimination) wäre denkbar, aber in den nachfolgenden
Passes wie Loop und Combine passiert zu viel als daß das in CSE schon
abgeschätzt werden kann. Also irgendwann zwischen Combine und global
Alloc? Und die Kosten-Frage ist immer noch nicht gelöst...
Johann
Sebastian____ wrote:
> Hallo,> ich habe ein kleines Problem. Bei einenm Projekt mit einem Mega8 habe> ich den Flash fast voll.> Aber zwischen den beiden Compilervarianten 20060421 <-> 20090313 erzeugt> der WinAvr von 2006 einen 500byte kleineren code als der aktuelle> WinAvr.>> Die optimierung ist jeweils schon auf "s" eingestellt.> Aus dem Map file werde ich nicht richtig schlau, wie bekomme ich denn> eine Ausflistung aus dem COmpiler welche funktion wie viel speicher im> Controller belegt?
1
avr-nm --size-sort -S foo.elf | grep -i ' t '
Ohne das grep bekommst du alle Objekte, also auch die Daten. Ich lass
die aber immer separat anzeigen, also mit
So ich habe das jetzt mal etwas weitergehen Analysiert:
1. #include <avr/eeprom.h>
im aktuellen Winavr wird eeprom_write_byte, eeprom_read_byte als
funktion direkt eingebunden, im alten WinAvr tachen diese Funktionen
nicht auf.
Die funktionen werden im Programm so aufgerufen:
2. Viele funktionen werden nur "etwas größer" in folgenden Beispiel 2
byte von 16 auf 18
1
//ADC Wert Messen
2
uintgetAdc(ucharadc_input){
3
4
ADMUX=adc_input|ADC_VREF_TYPE;
5
_delay_us(1);//1us Pause für das stabilisierend der ADC Werte
6
ADCSRA|=0x40;// Start the AD conversion
7
while((ADCSRA&0x10)==0);// Wait for the AD conversion to complete
8
ADCSRA|=0x10;
9
returnADCW;
10
}
Mit dem Alten WINAVR wird daraus:
1
ADMUX=adc_input|ADC_VREF_TYPE;
2
bb2:87b9out0x07,r24;7
3
__ticks=1;
4
elseif(__tmp>255)
5
__ticks=0;/* i.e. 256 */
6
else
7
__ticks=(uint8_t)__tmp;
8
bb4:84e0ldir24,0x04;4
9
bb6:8a95decr24
10
bb8:f1f7brne.-4;0xbb6<getAdc+0x4>
11
_delay_us(1);//1us Paus für das stabilisierend er ADC Werte
12
13
// Start the AD conversion
14
ADCSRA|=0x40;
15
bba:369asbi0x06,6;6
16
// Wait for the AD conversion to complete
17
while((ADCSRA&0x10)==0);
18
bbc:349bsbis0x06,4;6
19
bbe:fecfrjmp.-4;0xbbc<getAdc+0xa>
20
ADCSRA|=0x10;
21
bc0:349asbi0x06,4;6
22
returnADCW;
23
bc2:84b1inr24,0x04;4
24
bc4:95b1inr25,0x05;5
25
bc6:0895ret
Mit dem neuen WinAvr:
1
ADMUX=adc_input|ADC_VREF_TYPE;
2
d86:87b9out0x07,r24;7
3
canbeachieved.
4
*/
5
void
6
_delay_loop_1(uint8_t__count)
7
{
8
__asm__volatile(
9
d88:84e0ldir24,0x04;4
10
d8a:8a95decr24
11
d8c:f1f7brne.-4;0xd8a<getAdc+0x4>
12
_delay_us(1);//1us Paus für das stabilisierend er ADC Werte
13
14
// Start the AD conversion
15
ADCSRA|=0x40;
16
d8e:369asbi0x06,6;6
17
// Wait for the AD conversion to complete
18
while((ADCSRA&0x10)==0);
19
d90:349bsbis0x06,4;6
20
d92:fecfrjmp.-4;0xd90<getAdc+0xa>
21
ADCSRA|=0x10;
22
d94:349asbi0x06,4;6
23
returnADCW;
24
d96:24b1inr18,0x04;4
25
d98:35b1inr19,0x05;5
26
}
27
d9a:c901movwr24,r18
28
d9c:0895ret
das Problem ist, das sich das so auf ein komplettes Programm mal
schanell auf 500Byte aufsummiert. Meine Main schleife ist zb. von
358byte auf 540byte angewachsen.
A. K. wrote:
> Das jetzt nicht wirklich dein Beispiel für Codeexplosion, oder? Exakt> ein Befehl mehr.
Es ist viel Kleinvieh, und das macht eben auch Mist ;-)
Das sieht hier unschuldig aus, aber wenn es quer über die Anwendung hier
und da ne Instruktion mehr gibt alle 10 Befehle, dann sind das schon
rund 10% mehr Code. Und das ist echt satt; zumal mit dem Mehrcode noch
nichtmal Laufzeit eingekauft wird, sondern stattdessen zur rumgetrödelt
wird.
Naja, bei gcc 4 muss man schon froh sein, daß er bei (ADCSRA & 0x10) ==
0 nicht auf die Idee kommt, nen 16-Bit-Wert 4x nach rechts zu
schieben... :o)
Wo in dem Beispiel der überflüssige Befehl herkommt ist nicht
nachvollziehbar, dazu müsste man Compiler-Ausgaben sehen (zB mit -S
-dp). Wahlscheinlich kommt der move von pass greg, das ist einer der
kompliziertesten, vertracktesten und verhacktesten Teile von gcc.
Momentan ne riesen Baustelle. In 4.4.0 gibt's ein neuen
Register-Allokator. Bis dessen Kinderkrankheiten ausgewachsen sind, wird
aber auch ein Weilchen dauern.
Sebastian____ wrote:
> So ich habe das jetzt mal etwas weitergehen Analysiert:> 1. #include <avr/eeprom.h>> im aktuellen Winavr wird eeprom_write_byte, eeprom_read_byte als> funktion direkt eingebunden, im alten WinAvr tachen diese Funktionen> nicht auf.>> Die funktionen werden im Programm so aufgerufen:>
Die ziehen die Prolog und Elipog-Funktionen nach sich, weil sie aus der
mit -fcall-prologues übersetzten Lib kommen. Schau mal nach
__elipogue_restores bzw. __prologue_saves oder so, sie bringen scon rund
100 Byte mehr.
Johann
Ich glaub ich spinne, ich gehe gerade das list file des Codes duch.
im Code gibt es die funktion "CopyDefaultDataToRam();".
Diese wird mehrere male aufgerufen.
Und was macht der Compiler. Der kopiert die Funktion einfach 2x stumpf
in das Listing. Der Alte WinAvr mach schön ein rcall auf die Funktion.
Ich habe erst mal ein paar Funktionen als "static" markiert, und siehe
da, der Code ist schon mal 200byte kleiner geworden, nur durch doppeltes
einlinken von Funktionen.
Sebastian____ wrote:
> Und was macht der Compiler. Der kopiert die Funktion einfach 2x stumpf> in das Listing.
Meinst du inlining? Lässt sich verhindern. Siehe GCC Optionen, eine
davon ist die schon erwähnte -fno-inline-small-functions. Eine andere
Möglichkeit ist irgendein __attribute__.
Sebastian____ wrote:
> Ich glaub ich spinne, ich gehe gerade das list file des Codes duch.> im Code gibt es die funktion "CopyDefaultDataToRam();".
Da wäre noch interessant, wie diese denn implementiert ist, dass
der GCC auf die Idee kommt, das Inlining könnte da was einsparen.
A. K. wrote:
> Die für's Inling verwendete Kalkulation vom GCC ist etwas fragwürdig.
Die Abkürzung A. K ist etwas fragwürdig.
Und Pauschalisierungen sind sowieso immer falsch. :-)
Hallo,
ich habe mal Testweise die funktion
CFLAGS += -fno-inline-small-functions
wieder rausgenommen.
bei
1
voidCopyDefaultDataToRam(void){
2
uchar*data;
3
data=(uchar*)&settings;
4
memcpy_P(data,&defaultSettings,sizeof(settings));
5
}
Die Funktion wird 2x in der Main aufgerufen.
1
#define noinline __attribute__((__noinline__)) //funktion nicht inline kompilieren
Wenn ich die funktion so definiere:
void CopyDefaultDataToRam(void), habe ich einen output von 5556bytes
Wenn ich die funktion so definiere:
noinline void CopyDefaultDataToRam(void), habe ich einen output von
5532bytes.
Aktiviere ich im Makefile: CFLAGS += -fno-inline-small-functions
wird der Code auf 5528byes kleiner.
Als Vergleich, der Alte WINAVR Compiliert den Code auf 5134 Bytes.
Seltsam, dass der Unterschied so groß ist. Die Funktion macht ja
weiter nichts, als die Parameter für ein memcpy_P() syntaktisch
aufzubereiten und dann memcpy_P() selbst zu rufen. Wenn ich Compiler
wäre, hätte ich das vermutlich auch inline erweitert. ;-)
Wie behebt man die Problematik mit den EEprom Block-Copy routinen ohne
mit ReadByte und WriteByte eine eigene BLock Copy Routine erstellen muß.
Die Problematik wurde hier:
Beitrag "WinAVR 20090313 Released"
bereits angesprochen. Das eeprom_read_block/eeprom_write_block sehr viel
Speicher verbrauchen.
Was passiert genau wenn man
CFLAGS += -mcall-prologues
in das Makefile einbindet? Was kann ich dann an funktionen nicht mehr
nutzen? Eine Dokumentation dazu konnte ich noch nicht ausfindig machen.
Wenn man das im Makefile aktiviert, wird der Code recht genau 100byte
kleiner.
Sebastian____ wrote:
> Was passiert genau wenn man> CFLAGS += -mcall-prologues> in das Makefile einbindet?
Der Overhead am Anfang und Ende einer Funktion wird kleiner, vor allem
bei solchen mit vielen zu sicherenden Registern. Weil Aufrufe von
Hilfsfunktionen statt Inline-Code verwendet werden.
Performance-Einbussen dürften gering bis kaum nachweisbar sein. Hat
sonst keine Auswirkungen.
Es scheinen sich mittlerweile 2 Gruppen zu bilden:
Die einen bleiben bei den 2006/2007er Versionen, da sie keine Lust haben
ihre Software und die makefiles anzupassen die andere updated immer auf
die neueste Version.
Könnte vielleicht mal jemand, der wirklich Ahnung von den ganzen
Compileroptionen hat, eine kurze Liste in dem
AVR-GCC-Codeoptimierung Artikel erstellen, in der die wichtigsten
Optionen und deren Vor und Nachteile/Risiken erläutert werden? Es
scheint ja leider mittlerweile so zu sein, dass man einige Optionen
nutzen muss, wenn der Compiler einen kompakten Code erzeugen sollte.
Für alte projekte verwende ich auch weiterhin die alten
Compilerversionen, für neue Projekte versuche ich wenn möglich auch die
aktuellen Compilerversionen zu nutzen.
Das kann man ja recht leicht per Virtuellem Ordner mit junction
umschalten.
Ich bin jetzt mit den ganzen Optimierungen soweit das ich von 12%
mehrcode auf 6% runter im Vergleich zu der alten WinAvr Version.
Bei Microsoft:
http://technet.microsoft.com/de-de/sysinternals/bb896768.aspx
gibt es das kleine Tool Junction.
Damit kann man Virtuelle Ordner Erstellen.
1. Alle WinAvr Versionen Installieren:
Winavr (alt) in C:\Programme\WinAvr installieren, dann den Ordner per
Hand in C:\Programme\WinAVR-20060421 umbennen.
Winavr (neu) in C:\Programme\WinAvr installieren, dann den Ordner per
Hand in C:\Programme\WinAVR-20090313 umbennen.
2. 2 Batch dateien zum Umschalten Anlegen:
WINAVR 2006
junction -d C:\Programme\WinAvr
junction C:\Programme\WinAvr C:\Programme\WinAVR-20060421
WINAVR 2009
junction -d C:\Programme\WinAvr
junction C:\Programme\WinAvr C:\Programme\WinAVR-20090313
3. je nach gewünschter Winavr Version einfach eine der beiden Batch
Dateien ausführen um die entsprechende Compilerversion in den Pfad
C:\Programme\WinAvr zu mappen.
Sebastian____ wrote:
> Bei Microsoft:> gibt es das kleine Tool Junction.
Oder einfach den absoluten Pfad angeben und es hat sich der Lack...
A. K. wrote:
> Sebastian____ wrote:>>> Was passiert genau wenn man>> CFLAGS += -mcall-prologues>> in das Makefile einbindet?>> Der Overhead am Anfang und Ende einer Funktion wird kleiner, vor allem> bei solchen mit vielen zu sicherenden Registern. Weil Aufrufe von> Hilfsfunktionen statt Inline-Code verwendet werden.> Performance-Einbussen dürften gering bis kaum nachweisbar sein. Hat> sonst keine Auswirkungen.räusper wenn ausser dem eeprom-Zeug keine Lib-Funktionen benötigt
werden, dann schlägt -mcall-prologues mit rund 100 Bytes zu Buche. Für
µC mit wenig Flash ist das sehr viel.
Jörg Wunsch wrote:
> Seltsam, dass der Unterschied so groß ist. Die Funktion macht ja> weiter nichts, als die Parameter für ein memcpy_P() syntaktisch> aufzubereiten und dann memcpy_P() selbst zu rufen. Wenn ich Compiler> wäre, hätte ich das vermutlich auch inline erweitert. ;-)
memcpy_P ist ne Nicht-Standard Lib-Funktion und damit ne Blackbox. Wie
will man ne Libfunktion inlinen? Für den Caller wird's aufwendiger, weil
alle call-clobbered Register leergeräumt werden müssen, woher wohl der
Mehrverbrauch resultiert. Für memcpy_P wär vielleicht ein builtin
sinnvoller, bzw. die Funktion in die libgcc2 zu nehmen. Damit wären die
Effekte explizit beschreibbar, ebenso wie bei den divmod und mul
Funktionen, die als transparente Calls umgesetzt sind.
Johann
Johann L. wrote:
> räusper wenn ausser dem eeprom-Zeug keine Lib-Funktionen benötigt> werden, dann schlägt -mcall-prologues mit rund 100 Bytes zu Buche. Für> µC mit wenig Flash ist das sehr viel.
Ok, missverständlich formuliert. Im Platz macht es natürlich schon einen
Unterschied. Je nach Programm in die eine oder andere Richtung.
Ich bezog mich da eher auf seine Frage "Was kann ich dann an funktionen
nicht mehr nutzen?"
Johann L. wrote:
>> Seltsam, dass der Unterschied so groß ist. Die Funktion macht ja>> weiter nichts, als die Parameter für ein memcpy_P() syntaktisch>> aufzubereiten und dann memcpy_P() selbst zu rufen. Wenn ich Compiler>> wäre, hätte ich das vermutlich auch inline erweitert. ;-)>> memcpy_P ist ne Nicht-Standard Lib-Funktion und damit ne Blackbox. Wie> will man ne Libfunktion inlinen?
Es ging ja darum, dass die Funktion CopyDefaultDataToRam inline
erweitert worden ist. Da diese (bis auf die Bereitstellung der
Parameter) praktisch einen direkten Aufruf von mempcy_P() als
einziges verbleibendes Element enthält, verstehe ich, dass der
Compiler zu dem Schluss kommt, dass man sie inline erweitern sollte.
Was ich nur nicht verstehe ist, dass daraus eine massive Vergrößerung
des Codes entstehen soll.
Jörg Wunsch wrote:
> Johann L. wrote:>> memcpy_P ist ne Nicht-Standard Lib-Funktion und damit ne Blackbox. Wie>> will man ne Libfunktion inlinen?>> Es ging ja darum, dass die Funktion CopyDefaultDataToRam inline> erweitert worden ist. Da diese (bis auf die Bereitstellung der> Parameter) praktisch einen direkten Aufruf von mempcy_P() als> einziges verbleibendes Element enthält, verstehe ich, dass der> Compiler zu dem Schluss kommt, dass man sie inline erweitern sollte.> Was ich nur nicht verstehe ist, dass daraus eine massive Vergrößerung> des Codes entstehen soll.
Ah, ok. Es gibt immer auch Grenzfälle. Wenn zum Beispiel nur ein
Register mehr gebraucht wird, das gcc aber nicht mehr hat, dann legt er
für die Funktion nen Frame an. Und dann wird's richtig teuer. Im Prolog,
im Epilog und bei den Zugriffen auf die Variablen, die im Frame leben.
Davon ab geschieht im Beispiel das Laden der Symbole in der Funktion,
das kostet schon ein paar Bytes. Klarheit würde aber nur die
Compiler-Ausgabe(n) bringen.
Johann
Johann L. wrote:
> A. K. wrote:>> Ist memcpy_P eine "echte" Funktion, oder selber wiederum inlined?>> memcpy_P gehört -- wie gesagt -- zur avr-libc. Und gegen eine Lib kann> man nur linken.
Das ist doch kein Argument gegen mögliches Inlining. Die Funktion könnte
schließlich in einer Header-Datei definiert sein, wie einige andere
Funktionen der AVR-Libc auch.
Stefan Ernst wrote:
> Das ist doch kein Argument gegen mögliches Inlining. Die Funktion könnte> schließlich in einer Header-Datei definiert sein, wie einige andere> Funktionen der AVR-Libc auch.
Ist sie aber nicht, sondern sie ist eine echte Biblitoheksfunktion,
die für den Compiler erst einmal eine Blackbox ist. Johann hat doch
den Link auf den Quelltext schließlich schon zitiert, und du kannst
seiner Einschätzung über die Wirkmechanismen gut und gern blind über
den Weg trauen, wenn du's selbst nicht besser weißt. Ich habe von
ihm schon GCC-RTL-Code gesehen...
Ich weiß, dass es eine echte Bibliotheksfunktion ist. Das habe ich ja
auch nicht in Frage gestellt, sondern seine Begründung.
A. K.:
Echte Funktion oder Inlining?
Johann L.:
memcpy_P gehört zur avr-libc, also kein Inlining.