Hallo allerseits, nachdem ich bis jetzt beim Assembler geblieben bin, dacht ich mir, ich schnupper mal in ein paar C Codes rein und versuche mir das anzueignen. Leider finde ic immermal einige sachen, die für mich wenig sinn machen oder wo ich doch eine schnellere und sinnvollere Implementierung in ASM gewählt hätte. meine erste frage ziehlt auf dieses Codebeispiel hin. SIGNAL (SIG_OVERFLOW0) { static unsigned char count_ovl0; unsigned char ovl0 = count_ovl0+1; if (ovl0 >= 39) { // mach was ovl0 = 0; } count_ovl0 = ovl0; } warum wird hier noch eine Variable ov10 eingeführt? reicht es nicht, nur mit count_ov10 zu arbeiten? die kann man doch genauso hochzählen. Wo ist der Vorteil in dieser Implementierung, bzw gibt es einen? Ich hätte es eher so implementiert: SIGNAL (SIG_OVERFLOW0) { static unsigned char count_ovl0; if (count_ovl0 >= 39) { // mach was count_ovl0= 0; } count_ovl0 += l; } Außerdem hab ich noch folgendes Beispiel aus dem zum C programm kreierten ASM Code gefunden: 104: if ((PINC & 0x03)) IN R24,0x13 In from I/O location CLR R25 Clear Register ANDI R24,0x03 Logical AND with immediate ANDI R25,0x00 Logical AND with immediate OR R24,R25 Logical OR BREQ PC+0x04 Branch if equal Hier ist doch das "ANDI R25,0x00" völlig überflüssig, oder seh ich da was falsch? Vielleicht kann mir ja jemand weiterhelfen. Vielen Dank und liebe Grüße
> ...oder wo ich doch eine schnellere und sinnvollere Implementierung > in ASM gewählt hätte. Klar, wirklich sorgfältiger Assembler-Code kann in der Tat schneller und kleiner (beim AVR oft dasselbe) sein als compilierter. Die Gründe für die Benutzung eines Compilers sind ja andere: . sehr viel weniger Wartungsaufwand (Erstellen, Debuggen, Pflege), ich würde wenigstens den Faktor 10 auf Grund eigener Erfahrung ansetzen . man findet schneller einen Einstieg in eine neue Maschine, ohne deren Befehlssatz von vornherein bis ins letzte i-Tüpfelchen durchschaut zu haben, und bekommt dann in der Regel bereits besseren Maschinencode, als man ihn selbst schreiben würde . ein vernünftig geschriebenes Programm in einer höheren Sprache hat einen deutlich besseren Autodokumentations-Effekt als ein Assemblerprogramm; die dadurch bei letzterem für die Wartbarkeit notwendige weitere Dokumentation schlägt sich dann wieder auf den Kostenaspekt von Punkt 1 nieder (sieh mal ,Kosten' hier ganz wertfrei: in deiner Freizeit ist das dann also einfach das Maß an aufgewändeter Zeit, auch wenn sie dir so und so keiner bezahlt hätte) > SIGNAL (SIG_OVERFLOW0) Heißt seit avr-libc 1.4 übrigens "ISR(TIMER0_OVF_vect)", nur nebenbei. > warum wird hier noch eine Variable ov10 eingeführt? reicht es nicht, > nur mit count_ov10 zu arbeiten? Ja, würde in diesem Falle wohl genügen. Aber (und hier hast du einen Unterschied zur Assemblerprogrammierung): der Compiler wird (mal unter der Voraussetzung eingeschalteter Optimierung, alles andere ist nicht Sinn der Sache) wohl aus beiden Implementierungen den gleichen Code erzeugen. Damit ist dem Programmierer mehr Wahlfreiheit gegeben in der Darstellung seines Programmes: wenn es dem Verständnis des Algorithmus dienlicher ist, eine Hilfsvariable zu benutzen, dann kann und sollte er das tun -- der Compiler ist dann ,,intelligent'' genug, diese in der tatsächlichen Implementierung wegzulassen oder anderweitig zusammenzufassen. Die hier gezeigte Implementierung hätte übrigens sehr viel Sinn für den Fall, dass count_ovl0 als "volatile" deklariert wäre, weil Zugriffe auf derartige Variablen vom Compiler nicht optimiert werden. In diesem Falle würde die Hilfsvariable ovl0 dann als eine Art ,lokaler Cache' funktionieren, um die Nicht-Optimierbarkeit von count_ovl0 nicht zu einem Nachteil für die ISR werden zu lassen. > Hier ist doch das "ANDI R25,0x00" völlig überflüssig, ... Ja, ist es. Das ist ein altbekanntes Problem des GCC: dieser Compiler wurde ursprünglich ausschließlich für 32-bit-CPUs konzipiert. Der C-Standard schreibt nun vor, dass die Datentypen ganzzahliger Ausdrücke nach bestimmten Regeln aneinander angepasst werden, wobei alle Teilausdrücke mindestens wie ein `int' zu bewerten sind, ggf. mit einem entsprechend größeren Typ (unsigned int, long, unsigned long, ...) falls notwendig. Damit werden auch 8-bit-Ganzzahlausdrücke intern erst einmal auf 16 bits erweitert. Nun ist der C-Standard ausdrücklich als sogenannter `as if'-Standard spezifiziert: die Regeln darin sind nicht als konkrete Implementierungsvorschrift aufzufassen, aber eine Implementierung muss gewährleisten, dass das Ergebnis sich genau so verhält, als ob (`as if') die Regeln exakt so implementiert worden wären. Wenn also beide Seiten nur 8-bit-Ausdrücke enthalten und die Implementierung feststellen kann, dass die oberen 8 bits in der gesamten Rechnung gemäß den festgesetzten Regeln keine Rolle spielen, dürfen sie auch einfach weggelassen werden. Nun kommen wir aber wieder zum vorigen Absatz: auf einer 32-bit-CPU spielt die erste Konvertierungsstufe aller 8-bit-Ausdrücke auf `int' keine Rolle. Auf manchen Maschinen sind gar 32-bit-Rechnungen (in Registern) schneller als 8-bit-Rechnungen (die dann im Ergebnis erst Teile des Registers maskieren müssen). Auf Grund seiner Geschichte hat der GCC daher in der Vergangenheit eher wenig Optimierungen in diesem Bereich erfahren, verglichen mit vielen anderen Bereichen (bei denen er teilweise Optimierungen vornimmt, die du als Assemblerprogrammierer so nicht auf Anhieb gesehen hättest). GCC 4.1.x ist nach meinen Erfahrungen in dieser Hinsicht ein wenig besser geworden als die 3er Familie, mit der du vermutlich noch arbeitest, aber auch hier muss man zuweilen schon einmal etwas ,,nachhelfen'', indem man sich den generierten Assemblercode ansieht und ggf. den C-Code hie oder da ein wenig umstellt. Manchmal hilft dabei auch eine eingschobene Hilfsvariable, die einen Teilausdruck aufnimmt und damit dem Compiler deutlich vorgibt, dass an dieser Stelle wirklich nur 8 bits benötigt werden. Das Wichtigste an einem Compiler (wirklich das Allerwichtigste) ist aber, dass er fehlerfreien Code erzeugt, d. h. dass die Sprachkonstrukte auch tatsächlich so umgesetzt werden, wie vom Sprachstandard vorgeschrieben. Nur so kann sich der Programmierer darauf verlassen, dass er beim Debuggen nicht etwa noch die Fehler des Compilers suchen muss, sondern seine eigenen. In dieser Hinsicht muss der GCC sein Licht nicht unter den Scheffel stellen. Mir ist derzeit ein einziger Fall bekannt, bei dem er für den AVR wirklich falschen Code erzeugt (bei der Übergabe sehr vieler oder sehr großer Parameter an Funktionen, bspw. 3 x uint64_t).
Hi deine Implementierung macht was anderes als die Originalimplementierung. Zum zweiten Beispiel: Das hat seine Ursprünge im C-Standard da vor allen Rechenoperationen alle Variablen mindestens auf int (beim AVR also auf 16 Bit) erweitert werden. Es gibt Compiler die tun das nicht (im Bereich der 8 Bitter ist das evtl sinnvoll), der GCC tut das. Du wirst von einem Compiler nie optimalen Code erhalten. Aber genauso wird er an gewissen Stellen Code erzeugen der weit besser ist als das was ein Mensch in ASM kodiert. Matthias
@Martin, das Codebeispiel ist ein typischer Fall für das volatile-Dilemma: Variablen, die ein Interrupt setzt und eine andere Funktion abfragt, müssen als volatile deklariert werden, sonst optimiert der GCC alle Abfragen außer der ersten gnadenlos weg. Dann aber wird auch der Interrupthandler aufgebläht, da auch dort die Variable bei jeder Verwendung ständig neu eingelesen und abgespeichert wird. Abhilfe ist dann für die Optimierung eine Zwischenvariable zu nehmen, die nicht volatile ist. Dies tut das Beispiel. Eine andere Abhilfe wäre, den Interrupthandler und die Abfragefunktion in verschiedene Objekte zu packen, wo die Variable einmal nicht volatile und einmal volatile ist. Damit erhält man den kürzesten Code. Da volatile nur den Compilevorgang steuert bleibt es für den Linker trotzdem die selbe Variable. Peter
Vielen Dank für eure Antworten, besonder die ausführliche Antwort von Dir, Jörg. Deine Vermutung, war richtig, es handelt sich bei count_ovl0 um eine volatile definierte Variable. Der sinn eines C Compilers ist mir soweit schon klar, dewegen möcht ich mich mit dem Thema gern auseinandersetzen. Kostet zwar erst einmal ein wenig leseaufwand und vor allem ein wenig übung, aber ich denke, den aufwand holt man beim debuggen und vor allem beim Widerverwerten von Codeschnipseln schnell wieder raus. Ich war nur verwundert, das ich so offensichtlich überflüssige befehle auf anhieb gesehen habe, auch ohne allzu viel erfahrung mit ASM bis jetzt gesammelt zu haben. Eine Frage hab ich noch, wo finde ich die Version des installierten GCC's? Ist die 4.1.x gleichzusetzen mit der Version des AVR Studios? da habe ich nämlich Version 4.1.2 build 472. 'nen GCC Compiler habe ich (glaub ich) nicht explizit installiert, ich glaub, der war im AVR Studio dabei. Vielen Dank noch einmal und 'nen schönen Abend wünsch ich
> Eine Frage hab ich noch, wo finde ich die Version des installierten > GCC's? avr-gcc -v > Ist die 4.1.x gleichzusetzen mit der Version des AVR Studios? Nö, AVR Studio hat mit GCC rein gar nichts zu tun, außer dass es (mittlerweile) eine Schnittstelle bietet, um den AVR-GCC einzubinden. > 'nen GCC Compiler habe ich (glaub ich) nicht explizit installiert, > ich glaub, der war im AVR Studio dabei. Nein, AVR Studio bringt keinen Compiler mit, nur einen (eher spartanischen) Assembler. Entweder hast du WinAVR noch mit installiert, oder du hast wirklich keinen Compiler.
> Eine andere Abhilfe wäre, den Interrupthandler und die > Abfragefunktion in verschiedene Objekte zu packen, wo die Variable > einmal nicht volatile und einmal volatile ist. Damit erhält man den > kürzesten Code. Prima Glatteis. Kannst dir ja mal die Mühe machen, im Standard nachzusehen, ob das Ergebnis "undefined" oder "unspecified" behaviour ist... Bis zu "implementation-defined" wird es jedenfalls kaum reichen. Erzähl' doch bitte einem C-Anfänger nicht so 'nen Quark. Die Variante, die er aufgezeigt hat, ergibt sauberen Code, der vom Standard ordentlich gedeckt ist. Sie löst beide Probleme: die Kommunikation zwischen ISR und Haupt-Kontext genauso wie die andernfalls fehlende Optimierung innerhalb der ISR, wenn man nur mit dem volatile qualifizierten Objekt arbeiten würde. Sie wird das Ganze letztlich mit minimalem Overhead tun (die temporäre Variable wird am Ende nur aus ein oder zwei Registern bestehen, nicht etwa aus separaten RAM-Zellen).
@Jörg, der Softwerker hier in der Firma fand die Idee aber garnicht so schlecht. Auch sieht der erzeugte Assembler einwandfrei aus und Warnings/Errors gabe auch keine. Aber wenn Du als GCC-Profi dagegen bist, werd ichs mal sein lassen. Peter P.S.: Ich benutze jetzt wieder die AVR-GCC 3.4.6. Version. Der 4.1.1. spart oft ein paar wenige Byte aber manchmal verhaut er sich so richtig und dann sinds viele Byte mehr. Über die Summe der Projekte war die Bilanz jedenfalls negativ (mehr Flash). Und außerdem gehen mir die ständigen Assemblerwarnungen auf den Keks. Ich kann daher nur raten, für konstant gute Compileergebnisse erstmal noch beim offiziellen WINAVR mit GCC 3.4.6. zu bleiben.
bei genauerem hinsehen ließ sich auch ein WinAVR auftreiben. Ich werd mir die aktuelle version mal anschauen, vielen dank noch einmal fürs geduldige erklären.
> Der 4.1.1. spart oft ein paar wenige Byte aber manchmal verhaut er > sich so richtig und dann sinds viele Byte mehr. Beispiele? > Ich kann daher nur raten, für konstant gute Compileergebnisse > erstmal noch beim offiziellen WINAVR mit GCC 3.4.6. zu bleiben. Wie lange ,,noch''? Die nächsten 3 Jahre? 5? 8? Begreif doch mal: solange du deine Probleme nicht zur Sprache bringst, mit sauber nachvollziehbaren Beispielen und im richtigen Forum (also in einem, wo die AVR-GCC-Entwickler wenigstens ansatzweise eine Chance haben mitzulesen -- avr-gcc-list at nongnu.org), wird sich nichts ändern, da kannst du noch so lange warten. ,,Von allein'' wird da kaum einer was tun. Eigentlich gibt es derzeit wohl nur einen einzigen richtig aktiven AVR-GCC-Hacker (Björn Haase). Wenn ihm irgendeine Optimierung zu dünn ist, dann repariert er sie (und benutzt das dann auch in produktivem Code). Den Support für ATmega256x haben wir auch ihm zu verdanken. Den Lösungsansatz hatten zwar zuvor bereits andere diskutiert, aber letztzlich war er es, der das dann umgesetzt hat. Wenn dir aber irgendeine Optimierung zu dünn ist, dann wird er davon wohl einfach mal gar nichts wissen. Jemand anders, der etwas daran tun könnte, ebenfalls nicht. Das nächste ,,offizielle WinAVR'' wird auf jeden Fall mit GCC 4.1.1 daher kommen (falls nicht zufällig noch ein 4.1.2 zuvor freigegeben wird, aber das sieht mir nicht so aus). Eric Weddington hat, soweit ich das mitbekommen habe, keinen Bock mehr, obsolete GCC-Versionen noch als Alternative mitzugeben, weil ihm der logistische Aufwand zu hoch ist, eine entsprechende Auswahl (,,Möchten Sie die Version von heute, von gestern, von voriger Woche oder von vor 3 Jahren installieren?'') in den Installer zu bauen.
@Jörg, > Beispiele? Ich mach da mal nen neuen Thread auf mit dem Beispiel. > Wie lange ,,noch''? Die nächsten 3 Jahre? 5? 8? Ehrlich gesagt, ich hab da überhaupt keine Ahnung, wie lange GCCs so zum Reifen brauchen. Ich weiß auch erst, daß ich die 3.4.6 habe, nachdem gesagt wurde, die sei ja uralt und die 4.1.x sei besser. Davor hat es mich einfach nicht interessiert. > wo die AVR-GCC-Entwickler wenigstens ansatzweise eine Chance haben mitzulesen -- avr-gcc-list at nongnu.org) O.k. werd ich machen. Sind ja schon 2 Probleme. Ich stelle das Beispiel aber erstmal hier rein. Peter
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.