Hallo, ich habe gerade ein bestehendes 8051er-Projekt nach CortexM0 portiert (STM32F051) und war doch überrascht über die Codegröße. Anders als häufig zu lesen erhalte ich deutlich mehr Code und das, obwohl ich den Funktionsumfang auf Cortex-M0-Seite reduziert habe: 8051er: 20354 Bytes STM32F051: 33388 Bytes und mit optimize size: 26392 Bytes Dabei habe ich schon auf dem Cortex-M0 sprintf und sscanf durch eigene Routinen ersetzt, davor waren es noch etliche 10k mehr. Noch ein paar Rahmenbedingungen: Compiler sind Keil C51 und CooCox mit GCC, uC sind ADuC847 und STM32F051R8 Wie sind Eure Erfahrungen damit? Hat jemand einen Tipp, woran man noch drehen kann? Vielen Dank Gruß Martin
Da lesen: http://www.mikrocontroller.net/articles/ARM_GCC#Code-Gr.C3.B6.C3.9Fe_optimieren Beitrag "Re: (ARM) GCC binary size viel größer als von Realview" Das wichtigste dabei ist das Disassemblisiseren und nachzusehen was da viel Platz verbraucht.
Solange man hauptsächlich mit den internen 256 Byte SRAM auskommt und vorzugsweise 8Bit-Variablen nimmt, ist natürlich der 8051-Code deutlich kompakter. Der 8051 ist ja ein CISC, d.h. erspart sich haufenweise umständliches Load-Store-Gedöns, wie es der ARM benötigt. Erst wenn man ihn zum 32Bit Emulieren zwingt und die Variablen nach XDATA auslagert (Large-Model), geht der 8051 in die Knie. Er ist auf kleine Anwendungen (2..16kB) optimiert.
Hallo, und Danke für die Antworten. @ Niklas: Du denkst also, dass es an einzelnen eingebundenen Funktionen liegt und nicht an der Compilerleistung oder an Notwendigkeiten des Cortex-M0? Zu deinen Links: - ich verwende kein C++ - manches mache ich schon, wie zB vom Compiler berechnen zu lassen oder union, gilt aber für beide uCs - manches verstehe ich nicht, zB das mit der leeren atexit-Funktion, ich versuche mal, mich schlau zu machen @Peter Ich habe meinen 8051er Quelltext auch schon mit Blick auf geringe Größe geschrieben, also Byte-Vars und data wo es geht. Mußte aber doch viel in xdata legen und verwende auch float. Früher habe ich häufig die Assemblerausgaben angesehen und hatte dort das Gefühl, dass ca 30% aus herumschieben in Register und Akku bestehen. Ich dachte eigentlich, dass das ein Punkt ist, wo die Cortex-M Codegröße sparen würden. Allgemein verstehe ich dich so, dass du denkst, es liegt an der Architektur. Vielen Dank Gruß Martin
Martin M. schrieb: > Du denkst also, dass es an einzelnen eingebundenen Funktionen liegt und > nicht an der Compilerleistung oder an Notwendigkeiten des Cortex-M0? So die Vermutung, hängt natürlich stark von deinem Quelltext ab. 32bit-Code ist naturgemäß größer (wobei Thumb2 da schon stark "komprimiert"), aber ein Gutteil der Optimierungsmöglichkeiten kommen daher dass der GCC "ungefragt" große Funktionen aus der libc/für die CRT reinnimmt. Martin M. schrieb: > das mit der leeren atexit-Funktion Ja das ist so ein Workaround gegen ungefragte Funktionalität. Martin M. schrieb: > also Byte-Vars und data wo es geht Tja wenn deine Variablen alle "klein" sind kann der Cortex nicht punkten... Das ist dann halt praktisch für den 8051 ;) Martin M. schrieb: > und verwende auch float Hättest du einen Cortex-M4F (mit FPU) genommen könntest du da vermutlich drastisch Programmspeicher & Laufzeit sparen ;-) Peter Dannegger schrieb: > erspart sich haufenweise umständliches > Load-Store-Gedöns, wie es der ARM benötigt. Diese strikte Vorgehensweise ist halt günstig für eine Pipeline und erlaubt eine höhere Taktfrequenz...
Niklas Gürtler schrieb: > Hättest du einen Cortex-M4F (mit FPU) genommen könntest du da vermutlich > drastisch Programmspeicher & Laufzeit sparen ;-) Eine FPU spart nur wenig Code. Beim 8051 kostet die float-Lib einmalig ~1kB, dann kannst Du sie beliebig oft aufrufen. Eine FPU spart daher haupsächlich nur Laufzeit.
Dazu kommt noch das daß das Thumb instruction set 16 Bit, bzw 32. Bit (Thumb2) Instruktionen macht, der 8051 aber nur 8 Bit. D.h. der Code ist per se schon mal mindestens doppelt so groß. Die Frage ist: Ist das so schlimm ? Wer nur als Hobbybastler unterwegs ist bekommt mit den kleinen Cortexen wesentlich mehr Leistung zum Fast selben Preis wenn man mal die Versandkosten mit einbezieht. Der günstigste 8051 kompatible (CIP-51) bei Mouser kostet 0,52 € und hat 512 Byte Ram und 2 kB Programmspeicher. Der günstigste Cortex kostet 0,80 € hat dafür aber schon 4 kB RAM und 32KB flash und ist um Welten besser ausgestattet. Wenn's wirklich um ¢ Beträge geht sind die PICs bei den 8-Bittern wesentlich günstiger als die 8051er. Es ist und bleibt immer eine Frage des: Was brauch ich wirklich und was kostet's. Im Normalfall wird man sich auf 1 - 2 Architekturen "Einschießen" (sprich Know-How aufbauen) und damit seine Problemchen lösen. Für extreme Anforderungen, sei es Leistungsfähigkeit, Preis oder auch einfach nur die Anforderung des Kunden (Egal was Ihr verbaut, solange es aus unserem Konzern kommt) kann man auch mal eine exotischere Architektur hernehmen, allerdings wird dadurch der Entwicklungsaufwand durch die Einarbeitung höher und der will ja auch erstmal bezahlt werden. Gruß Heiko
Martin M. schrieb: > ich habe gerade ein bestehendes 8051er-Projekt nach CortexM0 portiert > (STM32F051) und war doch überrascht über die Codegröße Habe auch schon viel portiert, und die Codegröße lässt sich schon in etwa gleich halten wenn man folgendes berücksichtigt: 1) Die ARM-Compiler erzeugen eher schnellen als kompakten Code (weil die meisten ARMs ja genug Flash haben) und die Bibliotheken der Hersteller und vom gcc sind nicht auf Codegröße optimiert. Das ist bei sehr kleinen ARMs ein Problem, daher wurden für die minimale Bibliotheken entwickelt z.B.: LPC810 (nur 1k RAM, 4k Flash): https://github.com/microbuilder/LPC810_CodeBase 2) Die ARMs sind Load/Store und brauchen daher mehr Code für Portzugriffe. Dafür können M3 und höher alle Befehle bedingt ausführen, was viele Jumps und Calls spart. Der M0 kann das nicht, daher ist M3 Code deutlich kleiner als M0 Code. Da preislich kein Unterschied ist, besser zum M3 greifen, wenn es auf die Codegröße ankommt z.B. LPC1114/M0 und LPC1313/M3 (8k RAM, 32k Flash) kosten beide 2,50
Niklas Gürtler schrieb: > aber ein Gutteil der Optimierungsmöglichkeiten kommen > daher dass der GCC "ungefragt" große Funktionen aus der libc/für die CRT > reinnimmt. ok, ich habe mal in der .map-Datei nachgesehen und habe gefunden, dass ich dort die floating point Routinen sowohl für single float als auch für double habe. Im 8051er-Projekt habe ich nur single float (kann mein Keil C51 nicht besser) und das möchte ich jetzt auch beibehalten, da ich viele float-Vars habe und die in ein EEPROM gespeichert werden, Doppelt so viele Bytes passen da nicht mehr rein. Wie kann ich dem GCC sagen, dass er nichts in double rechnen soll? Ich habe nur an einer Stelle das Wort double in meinen Quelltexten, das ist bei der selbstgeschriebenen sprintf. Die Elipse akzeptiert in GCC keine float, gab eine Fehlermeldung bei compilieren. Alle double Routinen zu entfernen würde schon einige kByte sparen. Vielen Dank Martin PS Dank an Lothar für den Tipp / Vergelich M0 - M3
Heiko Jakob schrieb: > Dazu kommt noch das daß das Thumb instruction set 16 Bit, bzw 32. Bit > (Thumb2) Instruktionen macht, der 8051 aber nur 8 Bit. D.h. der Code ist > per se schon mal mindestens doppelt so groß. Sorry, aber das ist ein Milchmädchenrechnung der schlimmeren Sorte. Die Codegrösse ergibt sich aus der Grösse der Befehle und der Anzahl der Befehle. Willst du allen Ernstes behaupten, dass im vom Compiler erzeugten Code einem 8051 Befehl stets genau ein ARM Befehl entspricht?
:
Bearbeitet durch User
Hi, installier dir mal das MDK-ARM von keil.com und setze das Projekt mit MicroLib auf.
Hallo, habe gerade mit Google einen Tipp gefunden: Standardmäßig erzeugt gcc alle floating point-Konstanten als double und führt dann entsprechende Umwandlungen durch. Abhilfen: hinter jede single-float-Konstante ein f: 1.23f Copmpileroption -fsingle-precision-constant verwenden das hat meinen Code mit einem Schlag um 5kByte kleiner gemacht. Dann habe ich hier im Link-Tab der Configurations ein Kästchen LTO = link time optimization, das bringt nochmal 1,5k. Weiss jemand, warum das nicht standardmäßig angehakt ist? Gruß Martin
Martin M. schrieb: > ok, ich habe mal in der .map-Datei nachgesehen Schau auch mal in die Disassembly. Martin M. schrieb: > Wie kann ich dem GCC sagen, dass er nichts in double rechnen soll? Indem du überall nur "float" und kein "double" verwendest, d.h. insbesondere keine double-Funktionen wie "sin" sondern nur float-Funktionen wie sinf. Das heißt auch:
1 | float x = getVal (); |
2 | float y = 1. / x; |
Hier wird in double gerechnet, da 1. ein double ist! 1.f schreiben um sicherzustellen dass in float gerechnet werden soll. Das ist alles C-Standard, hat nix speziell mit dem GCC zu tun. Martin M. schrieb: > Die Elipse akzeptiert in GCC keine > float, gab eine Fehlermeldung bei compilieren. Kann nicht, klappt wunderbar mit eclipse und GCC. Martin M. schrieb: > Weiss jemand, warum das > nicht standardmäßig angehakt ist? Weil das versehentlich Dinge wie den ISR-Vektor wegoptimieren könnte und eine Zeit lang etwas buggy war. Diese Option entspricht dem Compiler&Linker-flag -flto, das auch im o.g. Wiki-Artikel angegeben ist.
Niklas Gürtler schrieb: >> Die Elipse akzeptiert in GCC keine >> float, gab eine Fehlermeldung bei compilieren. > Kann nicht, klappt wunderbar mit eclipse und GCC. War unklar formuliert. Die Elipse wandelt grundsätzlich in double um und wenn man dann mit va_arg ein float holen will, geht es nicht. Der Compiler meckert da, ist evtl auch nur eine Warnung. Ich bin inzwischen auf dem Cortex M0 auf unter 20k gekommen, das sieht erstmal akzeptabel aus. Dank an alle für die Beteiligung an diesem Thread. Gruß Martin
Martin M. schrieb: > War unklar formuliert. Die Elipse wandelt grundsätzlich in double um Um klar zu sein - eclipse wandelt gar nichts um. Eclipse ist nur eine grafische Oberfläche. Das macht wenn schon der Compiler. Und der wandelt nicht automatisch in float um, lediglich haben Literale der Form 1.234 den Typ double, wenn man kein "f" anhängt. Martin M. schrieb: > und wenn man dann mit va_arg ein float holen will, geht es nicht. Das kann auch nicht, da musst du was falsch gemacht haben. printf kann das seit Jahr und Tag...
A. K. schrieb: > Heiko Jakob schrieb: >> Dazu kommt noch das daß das Thumb instruction set 16 Bit, bzw 32. Bit >> (Thumb2) Instruktionen macht, der 8051 aber nur 8 Bit. D.h. der Code ist >> per se schon mal mindestens doppelt so groß. > Sorry, aber das ist ein Milchmädchenrechnung der schlimmeren Sorte. Die > Codegrösse ergibt sich aus der Grösse der Befehle und der Anzahl der > Befehle. Willst du allen Ernstes behaupten, dass im vom Compiler > erzeugten Code einem 8051 Befehl stets genau ein ARM Befehl entspricht? Da der Cortex auf der einen Seite jede Menge Load/Store Geschichten braucht, auf der anderen Seite aber auch alle Befehle bedingt ausführen kann sollte sich das nicht viel schenken. Daher kann man das sehr wohl als Abschätzung annehmen. Sorry aber was wolltest Du mit Deinem Post zum Thema beitragen ausser rumzustänkern ?
Hallo, seit wann kann der Cortex M0 alle Befehle bedingt ausführen? Also der 8051 ist ungebrochen mit sehr effizientem Code in Assembler zu programmieren. Er hat eine höhere Codedichte als der Cortex M0. Er ist langsamer als ein Cortex M0 und insgesammt auch von der Peripherie nicht so leistungsstark. Dafür gibt es Derivate von Silabs die mit unglaublich wenig Strom auskommen. Trotzdem sind die neuen CM0 Derivate von Freescale auch sehr interessant. Aber meine Empfehlung für Anfänger -> Cortex M0. Da der Speicheraufbau um einiges einfacher gehalten ist und man auch schnell mal zu CM3....CM4.....CM4F wechseln kann. Gruß Sascha
Martin M. schrieb: > 8051er: 20354 Bytes > STM32F051: 33388 Bytes und mit optimize size: 26392 Bytes Es ist schon ein paarmalweiter oben geschrieben worden, aber du hast es offenbar nicht gelesen: Lade dir mal den "Keil" herunter, bis 32 K Code geht der als Demo und compiliere damit dein Projekt. Der Compiler beim Keil ist der originale von ARM und der erzeugt schlichtweg besseren Code (kleiner + schneller) als der GCC. Wenn du hingegen partout beim GCC bleiben willst, dann versuche mal alle verschiedenen Optimierungsstufen. Ich hatte da schon seltsame Dinge gesehen. Ansonsten gilt die alte Regel, daß es wenig nützt, wenn der Quellcode mal eben portierbar ist - das damit gemachte Projekt ist in der Regel nicht genauso portierbar und es braucht bei einer anderen Hardware eben auch eine entsprechende Überarbeitung, wo die diversen Workarounds für die bisherige Plattform rausgeschmissen werden und Funktionalitäten umgestellt werden. Beispiele: alt: 1 Counter muß für alles herhalten, neu: ein Dutzend Counter vorhanden, also sharing überflüssig. Oder: alt: Datentransport per CPU-Loop, neu: selbiges per DMA. usw. W.S.
Heiko Jakob schrieb: > Sorry aber was wolltest Du mit Deinem Post zum Thema beitragen ausser > rumzustänkern ? Er wollte Dir lediglich klarmachen, wie unsinning die Annahme ist, dass ein 16 Bitter die doppelte Codegröße eines 8 Bitters braucht. Wenn das eine 'belastbare' Aussage wäre, sollte man gleich zum 1 Bitter greifen :-)
Er meinte zwar die Breite der Codeworte, nicht der Daten, aber das ist ebenso unsinnig.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.