Dieser ArtikelBenutzerSuche |
AVR-GCC-CodeoptimierungEntstanden aus dem Thread http://www.mikrocontroller.net/topic/66690 sollen hier ein paar Hinweise gegeben werden um den Quellcode in Punkto Größe und Geschwindigkeit zu optimieren. [bearbeiten] Optimierung der Größe[bearbeiten] GCC-interne OptimierungIm AVR-Studio kann bei den Projekteinstellungen der gewünschte Optimierungsgrad eingestellt werden. Hier gibt es folgende Möglichkeiten:
[bearbeiten] Nullinitialisierung von statischen VariablenBisher hatte ich in meinen Init-Funktionen nicht auf den Startup-Code verlassen, sondern die nötigen Variablen genullt. So kann ich auch eine Re-Initialisierung machen, habe aber keinen Gebrauch davon gemacht. Stattdessen starte ich komplett neu. Entfernung der Null-Inits brachte einiges. (OK, kann man doch als Anfängerfehler bezeichnen, ich mußte erst im C-Standard nachlesen daß das BSS-Nullen keine Eigenart von gcc ist.) [bearbeiten] Statische (globale) Variablen in ein struct sammelnDas erleichtert dem Compiler die Adressierung, da er den Basiszeiger wiederverwenden kann. Die Codegröße kann dann noch von der Reihenfolge der struct-Member abhängen Die häufigst benutzte Variable sollte am Anfang stehen, dann kann sie ohne Offset direkt mit dem Basiszeiger adressiert werden. Ansonsten in Gruppen, wie die Variablen auch gebraucht werden. Hier kann man viel rumprobieren... Beispiel:
Dadurch, dass die Strukturvariable über LDD/STD (LDD/STD 2 Bytes; LDS/STS 4 Bytes) angesprochen werden kann, werden an dieser Stelle 4 Bytes eingespart. Hinzu kommen jedoch noch einmal die 4 Bytes für die Initialisierung des Z-pointers, sodass die Einsparung erst bei mehreren Globalvariablen zum Tragen kommt. [bearbeiten] Multiplikationen mit KonstantenDer Compiler instanziiert sofort eine teure allgemeine Bibliotheksfunktion, auch wenn es anders ginge. Ich hatte eine einzige 32-bit Multiplikation mit 10 drin, die mir ein mulsi3 beschert hat. Mit a = (b<<3) + (b<<1) geht es in dem Fall kürzer. Wie gesagt, map-File beobachten. [bearbeiten] Alle Variablen nur so breit wie nötigHatte ich eigentlich schon, nur an einigen wenigen Stellen war ich da etwas nachlässig. Mitunter reicht ein kleinerer Typ doch, wenn man z.B. vorher geeignet skaliert. Am besten nur die skalaren Typen aus <stdint.h> verwenden, das erleichtert auch das Folgende. [bearbeiten] Logische Operatoren werden auf int-Größe erweitertObwohl der AVR ein 8-Bit Controller ist, weitet der AVR-GCC an manchen Stellen Vergleiche von zwei 8-Bit Variablen auf 16-Bit auf. Als Beispiel sei dabei folgendes gezeigt:
Den zweiten Vergleich mit der Negation weitet der Compiler auf 16 Bit auf. Ein Cast verhindert dieses:
Die Einsparung an Speicher zwischen den beiden Versionen beträgt 12 Bytes. Außerdem ist die zweite Version um 6 Takte schneller. Achtung: Tatsächlich handelt es sich dabei nicht um ein Optimierungsproblem, sondern einen typischen Programmierfehler. Die beiden Varianten sind keineswegs identisch. Bei Variablen vom Typ uint8_t wird der Ausdruck (a == ~b) immer falsch sein (a=0x0000:0x00FF, ~b=0xFF00:0xFFFF). Dem Compiler ist allenfalls anzulasten, dass er nicht darauf hinweist. [bearbeiten] Compileroption -mint8 für 8-Bit Arithmetik als DefaultMit obigen casts überall sähe der Code ziemlich schlimm aus. Blöd auch, wenn man mal einen Type ändert, dann muß man sorgsam nach den zugehörigen casts suchen. Mit dem Compilerschalter -mint8 wird das zum Standard. Bei mir hat das etwa 200 Byte gespart! Man sollte dafür aber keine ints mehr im Code haben, nur noch Typen definierter Größe aus <stdint.h>. Literal-Werte muß man ggf. anpassen (z.B. mit postfix L long machen) damit sie nicht überlaufen, Compiler-Warnings beachten. Ist anscheinend noch etwas experimentell(?), mit dem aktuellen gcc 4.1.1 gibt es eine Unverträglichkeit in <stdint.h>, der kriegt ein Problem mit den 64-bit Typen. Ist aber wohl in Arbeit, ich habe einen Patch gesehen. Die meisten Funktionen der avr-libc vertragen sich nicht mit -mint8! [bearbeiten] Stack auf 256 Bytes begrenzenMit dem Compileflag -mtiny-stack wird für den Stack eine einfachere Adressierung möglich, die aber "nur" 256 Byte Stacktiefe erlaubt. Wenn man nicht exzessiv automatische Variablen benutzt (Arrays!) oder eine hohe Verschachtelungstiefe hat, sollte das ausreichen. Hat mir nochmal knapp 100 Byte (!) kleineren Code erzeugt. [bearbeiten] Prolog/Epilog von main() verkleinernmain() ist für Embedded-Anwendungen unnötigerweise eine normale Funktion. Das könnten wir nur dann gebrauchen, wenn main() rekursiv aufgerufen wird, und das wäre auf einem Mikrocontroller nicht sonderlich sinnvoll. Schon eine einfache Endlosschleife for(;;) am Ende läßt den Compiler wohl erkennen, das er sparen kann, wird 4 Byte kürzer. __attribute__((noreturn)) habe ich nicht hinbekommen. [bearbeiten] Speichern von globalen FlagsOft werden in den Programmen Flags verwendet um beispielsweise eingetroffene Interrupts in der main-Routine auszuwerten. Hierzu wird üblicherweise eine globale Variable verwendet. Um den Wert dieser Variable abzufragen, muss sie jedoch erst aus dem SRAM in ein Register geladen werden, und kann dann erst auf ihren Status hin überprüft werden. Eine Möglichkeit ist, der globalen Variablen ein einziges Register fest zuzuordnen:
siehe auch: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_regbind Als Alternative kann man ein nicht verwendetes Register des I/O-Bereichs verwenden. Dabei würde sich z.B. das Register eines zweiten UARTs, oder das EEPROM-Register anbieten, falls diese nicht benötigt werden. Neuere AVR-Modelle besitzen für diesen Zweck 3 frei verwendbare Bytes im bitadressierbaren I/O-Bereich: GPIOR0-2. [bearbeiten] SchleifenBei Schleifen, die eine bestimmte Anzahl an Durchläufen ausgeführt werden sollen, ist es besser den Schleifenzähler vorher auf einen Wert zu setzen, und am Ende einer Do-While Schleife den diesen zu dekrementieren. So kann beschränkt sich die Sprungbedingung auf ein brne (branch if not equal). Beispiel:
[bearbeiten] Ein paar SchlagworteEs gibt noch -ffreestanding, soll noch ein pragma für main() geben welches Prolog/Epilog kappt, vielleicht kann man die Vektortabelle beschneiden. Mit -mcall-prologues werden die mitunter recht langen push/pop-Sequenzen in komplexen Funktionen durch Hilfsfunktionen ersetzt. Das kann vor allem bei grossen Programmen Platz sparen. Switch-Statements werden durch -mno-tablejump manchmal deutlich kürzer. [bearbeiten] Optimierung der AusführungsgeschwindigkeitHierzu gibt es schon eine Application-Note von Atmel: AVR035: Efficient C Coding for AVR Evtl. auf deutsch hier übersetzen? |