Hallo,
mal 'ne blöde Frage: Gibt es im AVR-Studio eine elegante Möglichkeit,
sich die Größe des vom GCC erzeugten ASM-Codes anzeigen zu lassen, außer
der Ausgabe nach dem Kompilieren?
Mir geht das ständige "Kompilieren und Ergebnis vergleichen" auf den
Zeiger und ich versuche noch ein paar Byte Flashspeicher aus dem
Kompilat zu quetschen.
Sonst noch irgendwelche guten Ratschläge (außer die, die im Artikel
Code-Optimierung bzw. im AVR-Datenblatt stehen), wo man nochmal nach ein
wenig Speicher auf die Suche gehen kann? Was verbraucht i.d.R. viel
Flash-Speicher, was nicht?
Und ja, die Optimierung steht auf -Os! ;)
Regler schrieb:> Hallo,>> mal 'ne blöde Frage: Gibt es im AVR-Studio eine elegante Möglichkeit,> sich die Größe des vom GCC erzeugten ASM-Codes anzeigen zu lassen, außer> der Ausgabe nach dem Kompilieren?>> Mir geht das ständige "Kompilieren und Ergebnis vergleichen" auf den> Zeiger und ich versuche noch ein paar Byte Flashspeicher aus dem> Kompilat zu quetschen.
Ähm.
Zettel daneben hinlegen und die Zahl notieren.
Dann weiß man sofort durch Vergleichen obs mehr oder weniger wird.
> Sonst noch irgendwelche guten Ratschläge (außer die, die im Artikel> Code-Optimierung
Die vergiss gleich mal wieder. 80% der dort gegebenen Ratschläge stammen
aus alten Zeiten und sind schon lange nicht mehr aktuell. Wahrscheinlich
waren die überhaupt nie aktuell.
Regler schrieb:> Zur Klarstellung, ich hätte gerne eine Anzeige>
1
>a+b;// z.B. 2 Byte
2
>a*b;// z.B. 150 Byte
3
>
> oder so ähnlich...
Die gibt es nicht. Denn je nach Zusammenhang kann der Compiler die
Operationen hin und herschieben und wenns ganz toll kommt, fallen sie
komplett raus.
Mann kann den Assembler Output studieren um zu sehen, wo der Compiler
dem Gefühl nach eher umständlich übersetzt hat und sich dann auf die
Suche begeben, welches C-Faktum man übersehen bzw. übersehen hat und so
den Compiler am besseren Optimieren gehindert hat.
Aber normalerweise bringt es mehr, wenn man auf Hochsprachenebene
anfängt und sich die benutzten Verfahren genauer überlegt.
Karl heinz Buchegger schrieb:> Ähm.> Zettel daneben hinlegen und die Zahl notieren.> Dann weiß man sofort durch Vergleichen obs mehr oder weniger wird.
Das meinte ich doch. Genau das mach ich. Durch Abhängigkeiten etc. ist
es aber nicht gerade effektiv, da die Änderungen am Code teilweise
umfangreich ausfallen und die Erkenntnis/Zeit gering bleibt...
Eine Ausgabe, in der Mann sieht, welcher C-Code welchen ASM Code erzeugt
hat, wie man ihn ja manchmal auch in Tutorials & Threads findet, gibt es
also beim GCC nicht?
Damit ließen sich die problematischen Stellen schneller finden und
gezielt bearbeiten.
> Die vergiss gleich mal wieder. 80% der dort gegebenen Ratschläge stammen> aus alten Zeiten und sind schon lange nicht mehr aktuell. Wahrscheinlich> waren die überhaupt nie aktuell.
Da ich mich bereits ein wenig eingelesen habe und dies hier im Forum
bereits mehrfach Thema war, ist mir das nicht unbekannt gewesen.
Wenn die Tipps aber so kontraproduktiv und evtl. falsch sind, warum
existiert der Artikel noch? Und: Gibt es Artikel mit dem gleichen Thema
aber sinnvollen Ratschlägen?
Ein paar Fragen, die ich mir gerade stelle:
- Erzeugt die Verwendung von Bitfeldern zwar schnelleren, aber größeren
Code? Oder andersrum?
- Wie geht man am geschicktesten mit globalen Variablen um, wenn man
speicher sparen will?
- Welche Zugriffe erzeugen viel Code, welche nicht?
- Dito für Operanden, C-Strukturen (Funktionen, Auslagern in externe
Dateien, ...)?
- Ab wie vielen Zugriffen aus dem Programm ist das Auslagern in eine
Funktion sinnvoll?
Karl heinz Buchegger schrieb:> Mann kann den Assembler Output studieren um zu sehen, wo der Compiler> dem Gefühl nach eher umständlich übersetzt hat und sich dann auf die> Suche begeben, welches C-Faktum man übersehen bzw. übersehen hat und so> den Compiler am besseren Optimieren gehindert hat.
Frau auch? ;)
Im Ernst: Wo steht denn der Assembler-Code? Ich finde keine ASM-Datei in
meinem Output-Folder.
Ich muss gestehen, bisher hat mich ASM-Code wenig interessiert. Ich
müsste also selbst den Code auseinanderdröseln und raten wo was getan
wird? Kann man sowas als ASM-Anfänger überhaupt oder müsste man dafür
das Thema stark vertiefen?
Regler schrieb:> Eine Ausgabe, in der Mann sieht, welcher C-Code welchen ASM Code erzeugt> hat, wie man ihn ja manchmal auch in Tutorials & Threads findet, gibt es> also beim GCC nicht?
Natürlich.
Man kann vom Compiler selbstverständlich auch das Assembler Listing
anfordern ....
>> Damit ließen sich die problematischen Stellen schneller finden und> gezielt bearbeiten.
.... nur sagt mir meine Kristallkugel jetzt schon, dass du damit nicht
glücklich werden wirst :-)
> Wenn die Tipps aber so kontraproduktiv und evtl. falsch sind, warum> existiert der Artikel noch?
Gute Frage
> Und: Gibt es Artikel mit dem gleichen Thema> aber sinnvollen Ratschlägen?
AFAIK nein.
> Ein paar Fragen, die ich mir gerade stelle:> - Erzeugt die Verwendung von Bitfeldern zwar schnelleren, aber größeren> Code? Oder andersrum?
Es erzeugt genau den gleichen Code, den du selber auch mittels
Variable |= ( 1 << Bitnummer );
generieren würdest.
Durch das Bitfeld versteckst du lediglich das Shiften und Verodern, also
die eigentliche arbeitende Ebene, vor dem geneigten Programmleser. Aber
zaubern können auch Bitfelder nicht.
> - Wie geht man am geschicktesten mit globalen Variablen um, wenn man> speicher sparen will?
Gar nicht.
Wenn du 60 Bytes globale Variablen brauchst, dann brauchst du 60 Bytes
globale Variablen. Da führt kein Weg drann vorbei. Man kann sich
höchstens fragen, ob tatsächlich alle Variablen global sein müssen, oder
ob einzelne Variablen nicht besser funktionslokal sein sollten.
Allerdings: Variable ist Variable. Irgendwo muss sie existieren. Manche
Variablen kann der COmpiler wegoptimieren, aber das ist dann eher
selten.
> - Welche Zugriffe erzeugen viel Code, welche nicht?
kann man so pauschal nicht sagen
> - Dito für Operanden, C-Strukturen (Funktionen, Auslagern in externe> Dateien, ...)?
Auch das kann man so pauschal nicht sagen
> - Ab wie vielen Zugriffen aus dem Programm ist das Auslagern in eine> Funktion sinnvoll?
Das hat damit nicht sehr viel zu tun. Eigene Funktionen können schon
rein aus Übersichtsgründen bzw. aus Gründen der Codestrukturierung
sinnvoll sein.
Wenn du ein wenig Code zeigst, dann könnten die hier ein paar Leute
helfen nach Flaschenhälsen zu suchen.
Regler schrieb:> Im Ernst: Wo steht denn der Assembler-Code? Ich finde keine ASM-Datei in> meinem Output-Folder.
Im AVR-Studio in den Project Options das Listfile einschalten.
Im Output Folder gibt es dann eine *.lss Datei
> müsste also selbst den Code auseinanderdröseln und raten wo was getan> wird? Kann man sowas als ASM-Anfänger überhaupt oder müsste man dafür> das Thema stark vertiefen?
Siehs dir an
Ich hab schon 5% gefunden, mit dem Compiler-Schalter:
-fno-inline-small-functions
Ich probier jetzt noch ein paar andere Schalter aus, liefert evtl. schon
genug Speicher. Wusste nicht, dass es da noch Möglichkeiten gibt, bin
gerade drüber gestolpert.... Hätte vielleicht noch Suchen müssen.
Danke Dir KH! Muss wirklich sagen, dass ich Deine Hilfe nicht zum ersten
Mal echt zu schätzen weiß. Bist ein Guter! ;)
Karl heinz Buchegger schrieb:>> Wenn die Tipps aber so kontraproduktiv und evtl. falsch sind, warum>> existiert der Artikel noch?>> Gute Frage>>> Und: Gibt es Artikel mit dem gleichen Thema>> aber sinnvollen Ratschlägen?>> AFAIK nein.
Wenn das jemand ändern kann, dann doch Ihr?
Für 1. würde ich eine Löschung oder eine deutliche Warnung am Anfang
vorschlagen.
Für 2. ein Artikel anlegen, indem jeder mal seine Erfahrungen und
eigenen kleinen Tests eintragen kann?
Ein Warn
Regler schrieb:>>> Und: Gibt es Artikel mit dem gleichen Thema>>> aber sinnvollen Ratschlägen?>>>> AFAIK nein.>> Wenn das jemand ändern kann, dann doch Ihr?
Ja.
Das Problem ist: Das ist ein zweischenidiges Schwert. Es kommt oft auf
die genauen Umstände an, was konkret funktioniert und was nicht. Und mit
der nächsten Compilerrelease kann sich vieles ändern.
Ich würde mal sagen:
Die richtigen Datentypen sind wichtig!
Auf einem PC ist das einfach. Da ist einfach alles ein int, ausser man
hat sehr große Zahlen, dann wirds ein long.
Auf einem AVR ist aber int teuer. Wenn Zahlen nicht größer als 128
werden können, dann auf jeden Fall int8_t. Wenn die Zahl kein Vorzeichen
haben kann, dann auf jeden Fall uint8_t benutzen! Alleine durch einen
unachtsam eingestreuten int kann man sich eine Menge Code aufzwirbeln,
der sofort verschwindet, wenn man bemerkt, dass es ein uint8_t auch
getan hätte.
Der GCC hat zwar manchmal so seine Schwierigkeiten, aus der von C
geforderten int-Rechnerei auf uint8_t abzuspecken, aber generell macht
er einen guten Job, wenn es um Optimierung geht. D.h. das Hauptaugenmerk
sollte auf algorithmischer Ebene liegen und nicht auf Kleinvieh. Darum
kümmert sich der GCC selber ausreichend gut.
Leider keine Steigerung mehr durch Compiler-Schalter erreicht.
Der eine hat ganz schön draufgehauen.
Gibt es da Verdächtige bei den standardmäßigen aktivieren Schaltern?
Bei mir waren das:
-fshort-enums
-fpack-struct
-funsigned-bitfields
-funsigned-char
-std=gnu99
-gdwarf-2
-Wall
Die Reihenfolge ist ja (hoffentlich) egal, oder?
Aber ein Blick in die lss-Datei...
AHA! Genau diese Ausgabe habe ich doch gesucht.
Wühle mich mal durch.
Nochmal Danke!
Karl heinz Buchegger schrieb:> Ja.> Das Problem ist: Das ist ein zweischenidiges Schwert. Es kommt oft auf> die genauen Umstände an, was konkret funktioniert und was nicht. Und mit> der nächsten Compilerrelease kann sich vieles ändern.
Was haltet ihr von Wettbewerben. Wer erzeugt den kleinsten Code? Oder
den schnellsten. Mit so ner ewigen Bestenliste, samt Code.
Als Beispiel irgendeine häufiger vorkommende Aufgabe, wie Tasten
entprellen, Sinus-PWM oder dergleichen.
Hardware vorgegeben, C sollte es sein, aber Compiler und Schalter, Art
der Programmmierung kann sich jeder selbst aussuchen... Da hätte man
gleich viele Beitragende und Erkenntnisse.
Nur so ne Idee.
Regler schrieb:> Was geht denn hier ab:
Kommt drauf an.
Was machst du in der ISR?
Funktionsaufrufe solltest du nach Möglichkeit vermeiden. Dann kann es
schon dazu kommen, dass der Compiler erstmal die halbe CPU sichern muss.
Daher ist es des öfteren besser, in der ISR nur ein Jobflag zu setzen
und die eigentliche Arbeit in der Hauptschleife zu machen.
Regler schrieb:> Das ist normal, oder?
Wenn du aus der ISR eine andere Funktion aufrufst, schon.
Oliver
P.S. Die Compileroption -Os nutzt du aber, oder?
Ja -Os verwende ich. Soweit ist klar...
Hab zwei Verdächtige aufgemacht:
- Die ISR erzeugen einigen Overhead, da ich aus Ihnen heraus Funktionen
fleißig aufrufe, um den Code besser zu strukturieren. --> Abhilfe Flags
und Hauptschleife verwenden.
- Verdacht, dass die Bitfelder mehr Schaden als Nützen. RAM hab ich noch
und Ausführungszeit ist unkritisch(-er). Mal schauen was das bringt.
Werde berichten.
Regler schrieb:> - Verdacht, dass die Bitfelder mehr Schaden als Nützen. RAM hab ich noch> und Ausführungszeit ist unkritisch(-er). Mal schauen was das bringt.> Werde berichten.
Nochmal 5% wegen einem Statusbyte als Bitfeld.... Habs jetzt auf Bytes
aufgeteilt.
Hc Zimmerer schrieb:> Kann einigen Platz sparen, wenn viele Funktionsaufrufe vorhanden sind> (auf Kosten der Geschwindigkeit):>> -mcall-prologues
Danke für den Tip. Hab ich schon probiert: Der einzige Schalter bei dem
der Code deutlich gewachsen ist....
Regler schrieb:> - Die ISR erzeugen einigen Overhead, da ich aus Ihnen heraus Funktionen> fleißig aufrufe, um den Code besser zu strukturieren. --> Abhilfe Flags> und Hauptschleife verwenden.
Hat leider nur 0.6% gebracht. Viel Arbeit, macht auch Sinn, aber leider
wenig Ergebnis. Hoffe keine Fehler eingebaut zu haben.
Übrigens kompiliert der Code mit 2% weniger Code, wenn ich auf ein Array
über ein Struct zugreife, als wenn ich das Array direkt benutze, ohne es
in ein Struct einzubetten.
Eigenartiges Ergebnis....
Hab jetzt weitere 10 Prozent zum Debuggen bzw. Ergebnis optimieren. Bin
aber für weitere Hacks dankbar...
Regler schrieb:> - Erzeugt die Verwendung von Bitfeldern zwar schnelleren, aber größeren> Code? Oder andersrum?
Solange es nicht konstante Ausdrücke sind, die zur Compilezeit berechnet
werden, sind Bitfelder recht umständlich.
Deshalb sieht man oft, daß für eine Bitvariable ein ganzes Byte benuzt
wird.
> - Wie geht man am geschicktesten mit globalen Variablen um, wenn man> speicher sparen will?
Vermeiden, d.h. lokale Variablen, wenn möglich.
> - Welche Zugriffe erzeugen viel Code, welche nicht?
Puh, da gibts keine kurze Antwort.
Z.B. variables Schieben ist teuer, kann man aber fast immer in Schieben
um 1 umformen.
> - Dito für Operanden, C-Strukturen (Funktionen, Auslagern in externe> Dateien, ...)?
Flache Strukturen, also möglichst wenige Zeiger auf Zeiger auf Zeiger
auf ...
> - Ab wie vielen Zugriffen aus dem Programm ist das Auslagern in eine> Funktion sinnvoll?
Ab 2.
Bewährte Optimierungsschalter sind:
-fno-inline-small-functions
-fno-tree-scev-cprop
-fno-split-wide-types
-Wl,--relax
--combine -fwhole-program
Peter
Peter Dannegger schrieb:> Solange es nicht konstante Ausdrücke sind, die zur Compilezeit berechnet> werden, sind Bitfelder recht umständlich.> Deshalb sieht man oft, daß für eine Bitvariable ein ganzes Byte benuzt> wird.
Was meine Optimierung bestätigt.(s.o.) Waren schon fast 5%. Hat mich
zwar 4 weitere Bytes gekostet, aber was solls...
> Vermeiden, d.h. lokale Variablen, wenn möglich.
Lustigerweise hab ich hierbei das Gegenteil erfahren: der Code wuchs um
4% durch das Einlagern einer struct-Variablen und übergeben des Zeigers
an die Funktionen, die Zugriff benötigten. Also global gelassen.
> Z.B. variables Schieben ist teuer,
Was meinst Du mit variabel? Zur Laufzeit festgelegt?
> kann man aber fast immer in Schieben> um 1 umformen.
Die Aussage verstehe ich nicht. Wie kann man Schieben um z.B. 7 mit
Schieben um 1 ersetzen? Verwende die Möglichkeit zu dividieren durch
Schieben fleißig...
> Flache Strukturen, also möglichst wenige Zeiger auf Zeiger auf Zeiger> auf ...
Das dachte ich auch, war aber beim Array in einem Struct (das sind ja
auch Zeiger-auf-Zeiger-Operationen, oder?) eben genau anders herum.
(s.o.)
> Bewährte Optimierungsschalter sind:> -fno-inline-small-functions
Brachte 5% s.o.
> -fno-tree-scev-cprop
0 Byte
> -fno-split-wide-types
0 Byte
> -Wl,--relax
0 Byte
> --combine -fwhole-program
Kompiliert leider nicht mehr....
Regler schrieb:> Kompiliert leider nicht mehr....
DAS HÄTTE EINE WARNUNG SEIN SOLLEN! !%#§$=?!
Das Programm war wohl etwas zuviel optimiert worden von den vielen
Schaltern... War irgendein Bug drin (......laaaaaaange gesucht, nichts
gefunden....).
Alle Schalter zurückgesetzt. Schwupps und schon funktionierts!
Ein Glück scheint mein Lieblingsschalter -fno-inline-small-functions
NICHT Schuld zu sein! Den hab ich dann doch wieder eingeschaltet.
Aber ab sofort bin ich ein gebranntes Kind, was diese Schalter angeht.
Musste ja einen Grund geben, dass die standardmäßig nicht alle gesetzt
sind!
Die meisten haben aber eh nicht viel gebracht.
Also: Seid gewarnt. Hat mich etwas arg viel Zeit und Nerv gekostet.
Peter Dannegger schrieb:> Z.B. variables Schieben ist teuer, kann man aber fast immer in Schieben> um 1 umformen.
Also sowas, wobei pwmmode eine Variable ist?
....
#define LEDPORT PORTB
#define LEDON(x) (LEDPORT &= ~(x)) // LEDs sind active low
#define LEDOFF(x) (LEDPORT |= (x))
....
LEDON(1<<pwmmode);
LEDOFF(1<<pwmmode);
...
Folgendes ist zwar kürzer, aber nicht wirklich viel:
LEDOFF(pwmmode?2:1);
Gibt es bessere Möglichkeiten?
Also hat sich gelohnt, trotz Ärger....
Fazit:
- Als allererstes Compilerflags "pimpen". Aber auch die Funktion des
Kompilats IMMER überprüfen (Ausführungszeit und Funktion). NICHT davon
ausgehen, dass sich am Ergebnis außer der Codegröße nichts ändert.
- Bitfelder: Finger weg. Viel mehr Code, aufwendiger. Sollte nicht mal
Zeit sparen. Lieber ganze Bytes für logische Operatoren verwenden.
- KEINE Funktionsaufrufe aus ISR. Aufwendig, selbst wenn klein.
- Main als non-returning definieren.
- Structs scheinen zur Organisation von Daten ganz nützlich zu sein.
- Bestätigt auch folgende Seite, die auch noch andere nützliche Tips und
Erklärungen hat:
http://www.tty1.net/blog/2008-04-29-avr-gcc-optimisations_en.html
- Ein Blick in die lss lohnt sich. Man kann gut erkennen, wo Mist gebaut
wurde.
Regler schrieb:> War irgendein Bug drin (......laaaaaaange gesucht, nichts> gefunden....).
Nochmal Ärger, wegen meiner Dummheit: Bug war (mal wieder) vorm PC.
Konnte mir auch nicht vorstellen, dass der Compiler schuld war.
Wenn man eine neue Configuration im AVR-Studio anlegt, erfolgt die
Ausgabe der hex-Datei in einem neuen Verzeichnis. Und das muss man
natürlich auch (selbst!!!) im Programmiermenü ändern. Wie konnte ich
übersehen, dass ich immer wieder und wieder das alte Programm geladen
habe.
Das konnte gar nicht funktionieren....
Regler schrieb:>> --combine -fwhole-program>> Kompiliert leider nicht mehr....
Dazu muß man dem Compiler alle Sourcen in einem Rutsch übergeben, also
nicht nacheinander.
Alternativ übergibt man nur ein C-File, was alle anderen included.
Oder compiliert nicht mit Make, sondern einer Batch, die das Argument
*.c expandiert.
Diese Optimierung geht sehr radikal über alles, entfernt unnötige Calls
und allen toten Code.
Peter
Was (nur beim WINAVR) sehr teuer ist, sind EEPROM-Zugriffe.
Da wird quasi ein BIOS installiert und von hinten durch die Brust ins
Auge aufgerufen (indirekter Call).
Deutlich kürzer und schneller geht es so:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=91306
Die Routine fängt sogar unnötige Schreibzugriffe ab.
Peter
Peter Dannegger schrieb:> Alternativ übergibt man nur ein C-File, was alle anderen included.
Etwa so:
#include "beispiel.h"
// und
#include "beispiel.c"
?
Peter Dannegger schrieb:> Deutlich kürzer und schneller geht es so:>> http://www.avrfreaks.net/index.php?name=PNphpBB2&f...>> Die Routine fängt sogar unnötige Schreibzugriffe ab.
Ich verwende tatsächlich den Eeprom.... Mmmmhhh. Schau ich mir mal
genauer an. Werde berichten.
Danke für die Hilfe, Peter! ;)
Peter Dannegger schrieb:> Regler schrieb:>>> --combine -fwhole-program>>>> Kompiliert leider nicht mehr....>> Dazu muß man dem Compiler alle Sourcen in einem Rutsch übergeben, also> nicht nacheinander.> Alternativ übergibt man nur ein C-File, was alle anderen included.> Oder compiliert nicht mit Make, sondern einer Batch, die das Argument> *.c expandiert.>> Diese Optimierung geht sehr radikal über alles, entfernt unnötige Calls> und allen toten Code.
WOW!
Peter!
Respekt.
Stellt alle anderen Mühen echt in den Schatten. Habe sofort fast 20%
gespart. Qualitativ sehr hochwertiger Tip und Empfehlung an alle, die
sich auch damit rumschlagen (müssen)... Hoffe der Code tut noch was er
soll.
1000 DANK und meine Empfehlung als Tipgeber des Monats!
Langsam kann ich wieder darüber nachdenken übersichtlich zu
programmieren ohne immer auf den Platz zu achten!
Den 2. Tip werde ich mir jetzt auch noch genau anschauen!
Regler schrieb:> Ich verwende tatsächlich den Eeprom.... Mmmmhhh. Schau ich mir mal> genauer an. Werde berichten.
2%... Da scheint bereits der Schalter ordentlich aufgeräumt zu haben
Regler schrieb:> Stellt alle anderen Mühen echt in den Schatten. Habe sofort fast 20%> gespart
Das konnte ja nicht wahr sein. Soooo schlecht hab ich auch nicht
programmiert. Ergebnis war einer umfangreichen Berechnung, die ich ins
Leere umgelenkt habe, geschuldet. Das scheint der Compiler mit diesem
Schalter zu erkennen und ganz wegzukürzen.
Daher alles einkommentieren und alle Funktionen verwenden, wenn man den
Schalter verwenden will.
Kann leider nicht im Nachhinein sagen, wieviel der Schalter Netto
gebracht hat. Sorry. Ist mir erst aufgefallen, als ich den Code
bearbeitet habe, kompiliert und stets die gleiche Code-Größe bekam.
Regler schrieb:> --combine -fwhole-program
Erfordert die Einbindung der zu den .h zugehörigen .c-Dateien.
Kann man die konditional (d.h. mit einer #ifdef-Anweisung) in
Abhängigkeit des Flags mit dem Präprozessor einbinden? Damit muss man
das nicht immer händisch ändern und hält den Code portabel.