Hallo allerseits, wie geht man am besten vor, wenn man sein lauffahiges Programm auf Codegrösse und/oder Geschwindigkeit optimieren möchte. Anders gefragt, was sollte man unbedingt tun oder lassen um Platz zu sparen. Gruß Codeoptimierer
In dem du das hier durchgehst: AVR-GCC-Codeoptimierung Ganz wichtig ist, immer nur kleine Schritte umzusetzen. Dann kompilieren und Codegröße checken. Dann führst du dein Programm aus. Das macht natürlich nur dann Sinn, wenn du in deinem Programm die Abarbeitungszeit messen und ausgeben kannst. Schließlich brauchst du Werte, die du vergleichen kannst.
Nicht zu fassen, jetzt treibe ich mich hier schon ne halbe Ewigkeit rum, aber den Artikel kannte ich noch gar nicht. Gruß Codeoptimierer
Optimieren ist eine 'schwarze Kunst' Das einfachste ist es, sich zunächst mal die Datentypen der beteiligten Variablen anzusehen. Muss es ein 16 Bit int sein oder reichen 8 Bit? Gibt es Tests (Bedingungen in if's), die sich gegenseitig abdecken? Kann man die alte Regel 'Space for Time' nutzbringend anwenden? Sowohl in die eine Richtung als auch in die andere. Ehe man dann weiter macht auf relativ niedriger Ebene zu optimieren: Zurücklehen. Die Frage lautet: Ist mein algorithmisches Verfahren generell gut geeignet? Gibt es Alternativen? Was würden die bringen? Gerade bei letzterem holt man oft die meiste Zeit raus: Optimieren durch Einsatz der grauen Zellen und kritischer Betrachtung des verwendeten Algorithmuses. Assembler-Listing ansehen Welche Codeteile sehen im Assembler Code so aus, als ob sie vom Compiler sehr aufwändig implementiert werden. Dann darüber nachdenken warum das so ist, welche (möglicherweise obskure) C Regel ist dafür verantwortlich, dass der Compiler nicht so optimieren kann, wie ich mir das vorstelle. Hatten wir letztens hier im Forum: Der Compiler konnte eine Division nicht durch Schiebeoperationen ersetzen, weil er nicht davon ausgehen konnte das die beteilgten Zahlen immer positiv sind. Ein entsprechender Hinweis (mit einem zusätzlichen unsigned) ermöglichte dann die Optimierung. Wenn auf Speicher optimiert werden muss: bei SRAM Knappheit: kann ich Strings sinnvollerweise aus dem SRAM ins Flash verbannen? Kann ich es mir leisten mehere Flag-Variablen in ein Byte zusammenzufassen, auch wenn dann die Zugriffe mglw. etwas langsamer werden. Dir obersten Regeln in der Optimierung lauten: "Premature optimization is the root of all evil" "Optimization: Don't do it!" "Optimization: Don't do it yet!" Auch wichtig bei Optimierungen: Identifiziere erst mal ob du überhaupt ein Problem hast, welches Optimierungsbedarf erfordert. Auf einem µC ist das zwar nicht ganz so krass, aber man geht davon aus, das rund 90% der Laufzeit eines Programmes von lediglich nur 10% Code hervorgerufen werden. Beim Optimieren ist es daher wichtig, diese 10% zu identifizieren. Dort bringt Optimieren was. Was jedoch nichts bringt, das ist eine Funktion um den Faktor 10 schneller zu machen, die von 1 Minute Programmlaufzeit lediglich 1 Sekunde verbraucht. Die Programmlaufzeit sinkt dann von 60 Sekunden auf 59.1 Sekunden. Das sind gerade mal 1.5% Laufzeitgewinn. Der Aufwand, die Funktion um einen Faktor 10 schneller zu machen ist aber meistens beträchtlich! Kann ich aber den Code, der für die 59 Sekunden verantwortlich ist um einen Faktor 10 schneller machen, dann sinkt die Gesamtlaufzeit von 60 Sekunden auf 6.9 Sekunden. Dort bringt Optimieren augenscheinlich viel mehr!
Lord Ziu schrieb: > In dem du das hier durchgehst: > > AVR-GCC-Codeoptimierung Hat eigentlich schon mal wer kontrolliert, welche der Tips in diesem Artikel mit dem aktuellen GCC noch Gültigkeit haben. Ein paar dieser Optimierungen erscheinen da sehr zweifelhaft.
Ok, soweit ist mir klar, das der erste schritt der Optimierung beim schreiben des Programms anfängt, z.B. wie in dem von Lord Ziu angehängten Link beschrieben, statt for schleifen auf do - while schleifen umzusteigen. Aber wie geht man vor wenn das Kind schon in den Brunnen gefallen ist. Die routinen die zu langsam sind findet man durch austesten raus. Aber wie findet man die grossen Speicherfresser? Gruß Codeoptimierer
Fortgeschrittener Anfänger schrieb: > Ok, soweit ist mir klar, das der erste schritt der Optimierung beim > schreiben des Programms anfängt, z.B. wie in dem von Lord Ziu > angehängten Link beschrieben, statt for schleifen auf do - while > schleifen umzusteigen. Gerade diesen Tip wage ich massiv in Frage zu stellen! Solche Sachen musst du immer mit deinem aktuellen Compiler ausprobieren! Und in der nächsten Compilerversion kann sich das alles wieder umdrehen. Generell solltest du davon ausgehen: "Die Compilerbauer sind keine Trotteln. Die verstehen von Optimierungen viel mehr, als ich das je könnte. Also überlass ich solche Low-Level Optimierungen lieber dem Compiler. Maximal seh ich mir an, ob ich nicht im C-Code dem Compiler den Weg für eine Optimierung verbaut habe, indem ich zb ungünstige Datentypen benutzt habe." (Der Tip mit der Multiplikation fällt zb auch in diese Kategorie. Ich wette, wenn man sich den Code ansieht den der Autor beschreibt, dann findet man im C Code die Ursache, warum der Compiler die Multiplikation nicht selbst durch Schieben ersetzen konnte. Compiler machen gerade solche Ersetzungen seit Jahrzehnten!) > Aber wie geht man vor wenn das Kind schon in den Brunnen gefallen ist. > Die routinen die zu langsam sind findet man durch austesten raus. > Aber wie findet man die grossen Speicherfresser? Punkt 1: map File studieren Punkt 2: Hilfsmittel einkaufen oder selber bauen. Es gilt rauszufinden, welche Funktion massig Stack durch lokale Variablen verbraucht. Stacktracer können das. Wenn man keinen hat, dann muss man sich eben selber einen bauen, indem man den Stackpointer mitlogt. Zur Not einen Code-Review machen: Alle Funktionen optisch durchgehen und die identifizieren, die viele Variablen anlegen. Dann die Aufrufhierarchie der Funktion feststellen: Wirken sich die vielen Variablen überhaupt aus oder ensteht mein Problem durch eine tiefe Funktionsaufrufhierarchie, bei der zwar wenige Variablen pro Funktion im Spiel sind, aber die Menge der ineinandergeschachtelten Aufrufe 'das Kraut fett macht'
Gut, den RAM Verbrauch für Globale Variablen finde ich im MAP-File unter Allocating common symbols. Anhand der PUSH und POPs müsste ich doch auch gut den "Stackverbrauch" abschätzen können, das geht über das LSS-File. Wo aber finde ich den Flash verbrauch für einzelne Funktionen, über das LSS file ist das ziemlich mühselig. Im MAP File finde ich es nicht, das scheint aber ein "Tomaten auf den Augen" Problem zu sein. Gruß Codeoptimierer
Fortgeschrittener Anfänger schrieb: > Im MAP File finde ich es nicht, das scheint aber ein "Tomaten auf den > Augen" Problem zu sein. Das seh ich auch so. Denn im Map File ist das (indirekt) aufgeschlüsselt. In der .text section sind alle Funktionen mit ihren Startadressen drinnen. Wenn du Probleme mit dem Flash hast und viele kurze Funktionen, könnte es etwas bringen, wenn du dem Compiler das inlinen von Funktionen verbietest. (Space for Time)
Fortgeschrittener Anfänger schrieb: > Aber wie geht man vor wenn das Kind schon in den Brunnen gefallen ist. Hast du ein konkretes Problem? Wenn ja, dann stells mal rein. Gerade bei eigenem Code ist man oft betriebsblind.
Hallo Karl Heinz, was bedeutet "inlinen von Funktionen" ? Ich habe das immer so verstanden das ich eine Funktion schreibe. Daraus wird ein Compilat in Assembler Das wird an eine Adresse gelegt. Und Funktionen die diese Funktion aufrufen brauche nur auf die Adresse zu springen. Nach beendigung der aufgerufenen Funktion wird zurückgesprungen zum "Aufrufer" Habe ich da was missverstanden?
Nö, ein konkretes Problem habe ich nicht, aber es passiert mir immer wieder das ich ein Progrämmchen geschrieben habe, was zwar tut was es soll aber einfach "gefühlt" zu gross ist. Das hinterlässt dann immer den faden Beigeschmack, das man zwar was geschafft hat, es aber immer noch nicht optimal ist. Als ich noch auf dem 8051 in Assembler Programmiert habe, hatte ich dieses "Gefühl" relativ selten. ;)
Fortgeschrittener Anfänger schrieb: > was bedeutet "inlinen von Funktionen" ? Das der Compiler den Aufruf einer Funktion durch den Funktionsrumpf ersetzt
1 | static int foo( int i ) |
2 | {
|
3 | return 2 + i; |
4 | }
|
5 | |
6 | int main() |
7 | {
|
8 | int j; |
9 | |
10 | j = foo( 5 ); |
11 | }
|
Ein perfekter Funktionsaufruf. Nur dauert das Aufrufen der Funktion, inklusive Register sichern und wiederherstellen etc. wesentlich länger als die eigentliche Abarbeitung der Funktion. Der Compiler wird so eine Funktion inlinen, d.h. er setzt den Funktionsrumpf an der Stelle des Funktionsaufrufs ein. Er compiliert also das hier:
1 | int main() |
2 | {
|
3 | int j; |
4 | |
5 | {
|
6 | j = 2 + i; |
7 | }
|
8 | }
|
und vereinfacht dadurch nebenbei den Code. Jetzt kann aber auch das hier passieren: * die Funktion hat einen gewissen Speicherverbrauch der Funktionsaufruf in main auch mit jedem zusätzlichen Aufruf dieser Funktion steigt der komplette Speicherverbrauch nur um das bischen was auf Aufruferseite notwendig ist um die Funktion aufzurufen * wird geinlined, so findet zwar kein Funktionsaufruf mehr statt der Code wird daher eingespart. Dafür taucht aber der Code der Funktion an jedem Funktionsaufruf erneut auf. Und zwar jedes mal. Wenn daher der Funktionskörper länger ist, als das was der Compiler benötigt um die Funktion aufrufen zu können, dann steigt mit jedem 'Aufruf' der Funktion der Speicherverbrauch um die Differenz.
Beispiel: Funktion ist 50 Byte groß und wird 10 mal im Programm aufgerufen. Ein Aufruf kostet 10 Byte: Ohne Inline: 10 * 10Byte + 50 Byte = 150 Byte Platzverbrauch Mit Inline: 10 * 50 Byte = 500 Byte Mfg Udo
Ahaaaa, jetzt wird es richtig interressant, genau soetwas habe ich in meinen Programmen im LSS file schon gesehen und konnte es mir nicht erklären. Wie gewöhnt man das dem Compiler ab? Ich würde gerne Testweise mal sehen was sich an der Codegrösse tut. Ist das Inlining standartmässig aktiviert?
Fortgeschrittener Anfänger schrieb: > Wie gewöhnt man das dem Compiler ab? Ich würde gerne Testweise mal sehen > was sich an der Codegrösse tut. Ist das Inlining standartmässig > aktiviert? http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html Einzubauen im AVR-Studio im "Project / Configuration Options" Dialog. Du willst -fno-inline PS: es heißt standard mit einem weichen d. Eine Standarte (mit hartem t) ist etwas ganz anderes.
Das war doch schonmal ein super Tipp, spart bei einem Programm von mir schonmal knappe ~500 Bytes im Flash. Gibt es noch mehr solcher "hinterhältingen" Tricks? PS: Ich weiss natürlich wie Standard richtig geschrieben wird, das verlängern von Wörtern lernt meine Tochter gerade in der Grundschule. Aber du machst mich mit der editiererei deiner Beiträge im nachhinein total "wuschisch" :)
Karl heinz Buchegger schrieb: > Lord Ziu schrieb: >> In dem du das hier durchgehst: >> >> AVR-GCC-Codeoptimierung > > Hat eigentlich schon mal wer kontrolliert, welche der Tips in diesem > Artikel mit dem aktuellen GCC noch Gültigkeit haben. Einige davon hatten noch nie Gültigkeit, da sie auf einer Atmel-AppNote für IAR basieren. Andere sind schlichtweg Unsinn, zB "register volatile". GCC hat intern überhaupt nicht die Möglichkeit, ein Register als volatile zu definieren. Aus gcc/rtl.h
1 | /* 1 in a MEM or ASM_OPERANDS expression if the memory reference is volatile.
|
2 | 1 in an INSN, CALL_INSN, JUMP_INSN, CODE_LABEL, BARRIER, or NOTE
|
3 | if it has been deleted.
|
4 | 1 in a REG expression if corresponds to a variable declared by the user,
|
5 | 0 for an internally generated temporary.
|
6 | 1 in a SUBREG with a negative value.
|
7 | 1 in a LABEL_REF, REG_LABEL_TARGET or REG_LABEL_OPERAND note for a
|
8 | non-local label.
|
9 | In a SYMBOL_REF, this flag is used for machine-specific purposes. */
|
10 | unsigned int volatil : 1; |
Tatsächlich hab ich schon beobachtet, daß in einer Schleife ein globales Register wegoptimiert wurde. gcc 4 benutzt lediglich life info zur Ermittlung der Lebensdauer etc. http://gcc.gnu.org/PR17336 http://gcc.gnu.org/PR34351 Globale Register können durchaus gewinnbringend eingesetzt werden, allerdings ändert man damit das ABI. Ich verwende zB globale Register in meinem Scope-Uhr Projekt, um x/y-Koordinaten zu transferieren. Um globale Register korrekt einzusetzen ist einiges an Wissen über den Compiler nötig, und es ist vor allem nötig, zu verstehen, was "global" in letzter Konsequenz bedeutet. Für den unbedarften Anwender, der globale Register handhabt wie globale (volatile) Variablen, ist die Chance, daß er sich selbst aufs Kreuz legt, nicht gerade klein. http://www.roboternetz.de/phpBB2/viewtopic.php?p=506013#506013 Auch -min8 bewirkt eine ABI-Änderung und wird zudem in neueren gcc-Versionen nicht weiter gepflegt. D.h. wenn's einen Fehler gibt, wird das eher dadurch behoben werden, daß -mint8 ganz rausfliegt, als daß der Bug gefixt wird. Beitrag "Re: Windows-Build von avr-gcc 4.5.0" Thema "Statische (globale) Variablen in ein struct sammeln". Damit kann deutlich Platz gespart werden, wenn das Umfeld stimmt. Es ist aber ebensogut möglich, daß man sich damit selbst ins Bein schiesst. Wieder ist Kenntnis über die Arbeitsweise/Vorliebe des Verwendeten Compilers notwendig. Als Checkliste mal dieses: (I) Über die verwendeten Algorithmen/Datenstrukturen etc. meditieren, s.o. (II) Sich mit ABI-konformen Compiler-Optionen vertraut machen, zb -Os, -O2, -fno-inline-small-function, -finline-limit=, -fno-split-wide-types (III) Sich mit der Arbeitsweise des Compilers vertraut machen, d.h.: wie sieht der Code aus, den er erzeugt. Wie gut ist er am angenommenen Optimum und welche Performance-Verluste sind akzeptabel weil der Verwendung eines Compilers geschuldet? Um die Codegüte beurteilen zu können dauert es ne Weile. (IV) Was kann an der Quelle ABI-konform geändert werden, zB Inlining, Verwendung von Attributen (const, pure, noreturn, always_inline), Verwendung von stdint.h. (V) Für welche Funktionen legt der Compiler einen Frame an? Welche Variablen leben im Frame und warum? Frames sind teuer, ebenso die Verwendung von Variablen, die darin leben: Es wird über den FP zugegriffen, man hal also kein Y-Reg mehr für eigene Varablen und damit ein wertvolles Pointer-Register weniger. Zur Verwendung müssen die Variablen geladen/gespeichert werden, der Frame muss auf-/abgebaut werden, etc. (VI) Wie kann die Quelle geändert werden, so daß sie besseren Code ergibt? Eine Quelle so hinzuschreiben, daß sie für einen bestimmten Compiler schmackhaft ist, ist nicht wünschenswert. Der Compiler sollte die Quelle gut übersetzen, nicht die Quelle sich gut an den Compiler anpassen. Insbesondere hat man bei Portierungen hier seinen Spaß... (VII) Gibt es Schalter, die einen Einfluss auf die Codegüte haben, aber das ABI verändert? (-funsigned-bitfields, -mint8, ...) Sind diese notwendig, erwünscht, unerwünscht, ...? (VIII) Welche Features können eingesetzt werden, die stark Hardware- und Compilerabhängig sind und evtl. nicht ABI-konform sind. Hierzu gehören -- (Inline) Assembler -- Globale Register -- Verwendung von GPRs wie GPIOR0 für globale Flags -- Verwendung transparenter Funktionen Bei diesem Punkt ist man i.w. auf sich selbst gestellt, weil die Infos, die im Netz zu finden sind, nicht selten inkorrekt, unvollständig oder missverständlich sind. s.o. Weiters sollte man folgende Punkte gegeneinander abwägen: -- Codeverbrauch -- Datenverbrauch. Statisch/Stack/Heap -- Mittlere/maximale Laufzeit -- Entwicklungszeit -- Portabilität (Compiler, Hardware, ...) -- Verständlichkeit der Quelle -- ABI-Konformität Generall dank ich, daß viele Optimierungen "Angst-Optimierungen" sind, die nicht wirklich nötig sind. Die Gefahr mit Optimierungen ist, den Code tot zu optimieren. Ich muss gestehen, daß ich selbst oft (zumindest in privaten Projekten) in die Optimierungs-Falle trete und optimiere, wo es nicht wirklich nötig ist, was zu Code führt, der schwerer wartbar ist.
Fortgeschrittener Anfänger schrieb: > Nö, ein konkretes Problem habe ich nicht, aber es passiert mir immer > wieder das ich ein Progrämmchen geschrieben habe, was zwar tut was es > soll aber einfach "gefühlt" zu gross ist. > Das hinterlässt dann immer den faden Beigeschmack, das man zwar was > geschafft hat, es aber immer noch nicht optimal ist. > Als ich noch auf dem 8051 in Assembler Programmiert habe, hatte ich > dieses "Gefühl" relativ selten. ;) Genau das meinte ich eben mit "Angst-Optimierung" :-)
Johann L. schrieb: > Code tot zu optimieren. Ich muss gestehen, daß ich selbst oft (zumindest > in privaten Projekten) in die Optimierungs-Falle trete und optimiere, wo > es nicht wirklich nötig ist, was zu Code führt, der schwerer wartbar > ist. Ist mir auch schon passiert. Ich hab an einem cleveren Triangulierer gearbeitet, der qualitativ gute Triangulierungen für Sonderfälle erzeugen sollte. War ein rekursives Divide and conquer Verfahren. Beim Testen ist mir aufgefallen, dass da viele geometrische Berechnungen doppelt, dreifach und n-fach gemacht werden. Also musst ein Cache her, der die Zwischenergebnisse aufnimmt. Ist aber nicht so einfach, denn es muss auch sicher gestellt werden, dass alte Werte aus dem Cache rausfliegen, wenn sich die geometrische Situation durch Aufbrechen des Poylgons verändert. Langer Rede kurzer Sinn: Das ganze wurde relativ komlpex, ich hab 2 Tage daran gearbeitet. Dann die Stunde der Wahrheit: Laufzeitvergleich! Ernüchternd ... der neue High-Tech Code war langsamer als der alte Bruth Force Code. Hätte ich gleich einen Profiler darauf angesetzt, hätte ich gesehen, dass die Rechenarbeit gar nicht das Problem darstellen. Die Lektion werd ich nie vergessen.
Ok, jetzt habt ich mich. Ich gestehe, ich bin ein Angst Optimierer. :) Ich habe nicht das riesen Problem einen Algorithmus für eine bestimmte Augabenstellung zu finden. Man zerlegt das Problem in teilaufgaben, versucht gemeinsamkeiten in einzelne Funktionen zu packen usw... Ich kann auch damit leben das jemand anderes vielleicht eine einfachere Lösung für dasselbe Problem findet. Aber was mich immer wieder wundert ist, man hat eine kleine übersichtliche routine geschrieben die genau das tut was sie soll und wieder sind 2K Flash futsch. Natürlich hilfts, wenn mal jemand anders drüberschaut, aber das kann man ja auch nicht jedes mal machen. Und deswegen wäre eine generelle Strategie von Vorteil, wie man Codeoptimierung betreibt.
In diesem Artikel stehts nochmal zusammengefasst: http://blogs.msdn.com/b/audiofool/archive/2007/06/14/the-rules-of-code-optimization.aspx Oliver
Fortgeschrittener Anfänger schrieb: > Aber was mich immer wieder wundert ist, man hat eine kleine > übersichtliche routine geschrieben die genau das tut was sie soll und > wieder sind 2K Flash futsch. Ich gesteh dir natürlich zu, dass du hier ein wenig übertreibst. Aber so schlimm ist es auch wieder nicht. Optimierer einschalten keine Floating Point Operationen Formeln umstellen und zusammenfassen uint8_t wo's nur geht. alleine damit hält sich der Flash-Verbrauch meistens schon ganz gut im Rahmen. Und einen kleinen Penalty zahlt man nun mal für den Compiler. Mit Assembler kann man natürlich noch das eine oder andere Byte einsparen, weil man zb. die SREG Flags cleverer einsetzen kann. Aber diese paar Bytes sind mir der Komfort eines Compilers allemal wert. > Natürlich hilfts, wenn mal jemand anders > drüberschaut, aber das kann man ja auch nicht jedes mal machen. Und > deswegen wäre eine generelle Strategie von Vorteil, wie man > Codeoptimierung betreibt. Wenn es eine gäbe, würden die Compilerbauer sie in die Compiler einbauen :-)
Karl heinz Buchegger schrieb: >> AVR-GCC-Codeoptimierung > Hat eigentlich schon mal wer kontrolliert, welche der Tips in diesem > Artikel mit dem aktuellen GCC noch Gültigkeit haben. > > Ein paar dieser Optimierungen erscheinen da sehr zweifelhaft. Einige davon sehen auf den ersten Blick so aus, als hätte sich da jemand erstmal überlegt, wie er selbst den Code in Assembler schreiben würde, und hat dann den Compiler solange "getriezt", bis endlich das gewünschte herauskam. IMO ist dieses Vorgehen ein komplett falscher Ansatz. Wenn man konkret ganz bestimmten Code wünscht/braucht, dann soll man ihn gefälligst auch in (Inline-)Assembler hinschreiben. Damit ist dann wenigstens auch garantiert, dass das mit der nächsten Compilerversion immer noch denselben Code ergibt. Natürlich soll das nicht heissen, dass man sich nicht den Assemblercode ansehen soll, um Optimierungspotential zu finden, und ggf. auch durch kleine Codeumstellungen oder Attributierungen dem Optimizer Hilfestellung zu geben. Man soll nur eben nicht versuchen Assembler als C hinzuschreiben, es sollte schon sauberes C bleiben. Andreas
Hi, Ich bin der Ansicht, wenn man auf einer Ebene gut sein will, sollte man Ahnung von der tiefer liegende Ebene haben. Ich hab schon viele Algorithmen (hauptsächlich Crypto) in Assembler optimiert implementiert, und dadurch gelernt wie man Code so schreibt, dass der Compiler da was anständiges raus machen kann. Das do{...}while(...); loop Konstrukt ist da ein gutes Beispiel. Die Verwendung von Schleifen dieser Art macht es dem Compiler möglich einfach guten Code zu erzeugen. Das liegt daran das diese Art von Schleife sehr einfach in wenige ASM Befehle umgesetzt werden kann (Codeblock; Condition-Code; konditionaler Sprung). Es hilft auch zu wissen, dass es nur 3 Pointer-Register gibt (wovon eins evtl. als Frame-Pointer benötigt wird). Auch die Adressierungmodi zu kennen hilft auch. Also mein Tipp für alle die ernsthaft optimierten Code produzieren wollen ist sich mehr mit der Maschine und der Maschinensprache zu befassen. Natürlich sind Wissen über den Compiler und seine Optionen ebenfalls sehr hilfreich um ihn zur Erzeugung guten Codes zu bewegen.
Daniel Otte schrieb: > Die Verwendung von Schleifen dieser Art macht es dem Compiler möglich > einfach guten Code zu erzeugen. Nun ja, ob es für den Compiler einfach ist, oder nicht, ist egal. Hauptsache, er macht es. Und ein anständiger Compiler sollte am Ende aus einer funktionell identischen Schleifen ähnlich effizienten Code machen, egal, ob da nun for, do, oder while vorne dran steht. Oliver
Daniel Otte schrieb: > Das do{...}while(...); loop Konstrukt ist da ein gutes Beispiel. Was soll denn der Vorteil sein bei do-while gegenüber while?
Simon K. schrieb: > Daniel Otte schrieb: >> Das do{...}while(...); loop Konstrukt ist da ein gutes Beispiel. > > Was soll denn der Vorteil sein bei do-while gegenüber while? Dass die Bedingung hinten steht. So brauchts auf dem AVR prinzipiell nur ein inc/dec und ein brne/breq/... Das Übertrags-Bit kann unmittelbar übernommen werden. Bei der while-Schleife wäre das (prinzipiell) inc/dec + brne/breq + rjmp (um wieder nach oben zu kommen). Aber GCC ist nicht doof.
Sven P. schrieb: > Aber GCC ist nicht doof. Ist er doch. Du kannst Dich auf den Kopf stellen, wenn Du einen Pointerzugriff mit Autoincrement machen willst (*ptr++), no chance. Er macht immer LD + ADIW + RJMP draus. Für ihn sind 3 Befehle schneller als einer. Peter
Peter Dannegger schrieb: > Sven P. schrieb: >> Aber GCC ist nicht doof. > > Ist er doch. > Du kannst Dich auf den Kopf stellen, wenn Du einen Pointerzugriff mit > Autoincrement machen willst (*ptr++), no chance. > Er macht immer LD + ADIW + RJMP draus. > Für ihn sind 3 Befehle schneller als einer. Hast du ein bestimmtes Beispiel? Manchmal hilft -fno-tree-loop-optimize
Johann L. schrieb: > Hast du ein bestimmtes Beispiel? Manchmal hilft -fno-tree-loop-optimize
1 | void eeprom_rw( uint16_t eep, uint8_t *sram, uint8_t len, uint8_t write ) |
2 | {
|
3 | uint8_t edat, sdat; |
4 | |
5 | do{ |
6 | EEAR = eep; |
7 | EECR |= 1<<EERE; // read |
8 | edat = EEDR; |
9 | if( write ){ // write or read action |
10 | sdat = *sram++; |
11 | if( sdat != edat ){ // if not equal |
12 | EEDR = sdat; // load data |
13 | ATOMIC_BLOCK(ATOMIC_FORCEON){ |
14 | EECR |= 1<<EEMPE; |
15 | EECR |= 1<<EEPE; // write |
16 | }
|
17 | while( EECR & 1<<EEPE ); // wait until write done |
18 | }
|
19 | }else{ |
20 | *sram++ = edat; |
21 | }
|
22 | eep++; |
23 | }while( --len ); // 1..256 byte |
24 | }
|
Im Anhang das Listing -fno-tree-loop-optimize braucht sogar 6 Bytes mehr. Peter
Bei mir hat das Einbinden der Mathbibliothek ohne sie wirklich zu brauchen schon des öfteren zu einer Verkleinerung der Codegröße geführt. Schreibe Routinen in einem Rutsch. Mir ist es bei Änderungen von Routinen schon mehrmals passiert, dass ich Sonderfälle behandelt habe, die ohnehin nicht zu dieser Stelle kommen. Also im Prinzip: Karl heinz Buchegger schrieb: > Gibt es Tests (Bedingungen in if's), die sich gegenseitig abdecken? Am besten zum Schluss noch mal die Routine codeblockweise durchgehen und überlegen: Was mache ich hier und warum? Alternativ vorher, oder zumindest nachher, ein Struktogramm / einen Programmlaufplan zeichnen. Aber wer will das schon. Ist die Behandlung von Sonderfällen wirklich nötig, oder lässt sich ein System finden, mit dem alle, oder zumindest mehr, Fälle auf die selbe Art und Weise verarbeitet werden können? Für die Wiederverwendung und Wartung natürlich sehr schlecht: Werden Fehlercodes, die zurückgegeben werden, überhaupt ausgewertet? Oder können unsinnige Eingangswerte ruhig unsinnige Ausgangswerte erzeugen? Wenn die Eingangswerte nur ungültig/unsinnig sind, wenn sowieso Hopfen und Malz verloren sind, bzw. wenn das viel besser schon beim Ermitteln dieser Werte kontroliert würde (und vielleicht auch schon wird), dann ist in der Routine die Behandlung solcher Fälle nicht nötig. Viel Spaß bei Änderungen beim Ermitteln der Werte oder wenn Du die Routine dann doch wo anders noch einmal verwenden willst.
Peter Dannegger schrieb: > Johann L. schrieb: >> Hast du ein bestimmtes Beispiel? Manchmal hilft -fno-tree-loop-optimize [...] In deinem Beispiel macht dir eine andere Optimierung eine Strich durch die Rechnung, vermutlich irgendeiner der "Redundancy Elimination"-Schritte. Der Inkrement findet in beiden if-Zweigen statt und wird deshalb aus der Verzweigung ausgelagert. Deshalb kann er dann in späteren Optimierungsschritten bei der Codegenerierung nicht mehr mit dem LD zusammengezogen werden. Nur zur Demonstration: kommentier mal den else-Zweig komplett aus, dann benutzt der gcc auch "LD rXX, Z+". Andreas
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.