Ich habe ein Programm für 8k (ATMega 8/28pin) welches jetzt knapp den ganzen Speicher belegt - noch 20 Byte frei. Bisher WinAVR/AVR-GCC von 2007. Und zwar ist es mit -O1 unterm Strich am besten. Mit -O2 bin ich schon überm Limit. -Os bringt zwar noch ein paar Byte, macht aber Division durch 256 etc. absurd mittels "rcall __divmodhi4" ! ;-) Das ist zuviel des Guten, auch wenn dieses Prog ansonsten nicht besonders zeitkritisch ist. Jetzt soll aber noch etwas Funktionalität rein/verbessert werden (ohne zuviel Kopfzerbrechen und Assembler) und ich dachte ich probier einfach erstmal den neuesten AVR-GCC (gcc version 4.3.3 (WinAVR 20100110)). Und nu: mit -O1 gleiches prog und Makefile, Make läuft durch (passiert mir selten bei Updates aller Art nach 4 Jahren) - aber "region text overflowed by 400 bytes" ! D.h. der neue GCC frißt einfach so mal 5% mehr Speicher - statt weniger wie erhofft. Mit -Os kommt er knapp so wie -O1 mit dem alten GCC, aber Division durch 2er-Potenzen sind immer noch inakzeptabel volldoof mit _divmod... (ich will nicht den Code durch unleserliche Shift&And Operatoren verhunzen) Auf den ersten Blick sehe ich auch sonst keine besonderen Verbesserungen in der Optimierung im .s File. Frage: Was ist der Hauptgrund warum der neue AVR-GCC scheinbar ohne Gegenleistung soviel mehr Speicher statt weniger frißt? Was kann man tu Per anschauen des generierten Assembler-Code sehe ich auf Anhieb vieles was ein "perfekter" Compiler viel schneller UND kürzen machen könnte...!? Was sind typisch die besten Methoden um Prog-Speicher zu sparen - mit minimax bzgl. Schreib- & Fuzzel-Denkaufwand ? Gruß
kxr schrieb: > Frage: Was ist der Hauptgrund warum der neue AVR-GCC scheinbar ohne > Gegenleistung soviel mehr Speicher statt weniger frißt? Was kann man tu Ein Grund ist z.B., dass neuere GCC aggressiver beim Inlining sind. Entweder ganz abschalten, oder die Größengrenze variieren. Am besten beides ausprobieren. Das Ganze war übrigens schon öfter Thema hier. Du könntest mal etwas suchen, denn irgendwo schwirrt auch eine Liste mit empfohlenen Compiler-Optionen herum. PS: -lm beim Linken benutzt du aber schon, oder?
-lm war nicht drin, änderte aber auch gar nix an den 400bytes. von libm wird auch nix benutzt. So nach Deinem Such-Hinweis wurden jetzt gemäß http://www.tty1.net/blog/2008-04-29-avr-gcc-optimisations_en.html Beitrag "optimale avr-gcc compiler optionen" (optimale avr-gcc compiler optionen) aber mal ordentlich Optionen aufgerüstet! alles was greifbar war - nicht alles schon verstanden. das mündet soweit in: avr-gcc -g -Wall -O1 -mmcu=atmega8 -I../avrspy -save-temps -fverbose-asm -mtiny-stack -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums --param inline-call-cost=2 -finline-limit=3 -fno-inline-small-functions -ffunction-sections -fdata-sections -ffreestanding -mcall-prologues -fno-tree-scev-cprop -fno-split-wide-types -c -o spc.o spc.c ... avr-gcc -g -Wall -O1 -mmcu=atmega8 -I../avrspy -save-temps -fverbose-asm -mtiny-stack -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums --param inline-call-cost=2 -finline-limit=3 -fno-inline-small-functions -ffunction-sections -fdata-sections -ffreestanding -mcall-prologues -fno-tree-scev-cprop -fno-split-wide-types -Wl,-Map,spc.map -Wl,--relax -o spc.elf spc.o avrspy.o c:/programme/winavr-20100110/bin/../lib/gcc/avr/4.3.3/../../../../avr/bi n/ld.exe: spc.elf section .text will not fit in region text c:/programme/winavr-20100110/bin/../lib/gcc/avr/4.3.3/../../../../avr/bi n/ld.exe: region text overflowed by 80 bytes => d.h. die Superoptionen haben von den 400 over wieder auf 80 bytes over runter gebracht. -O1 bleibt das beste. -Os ist nachwievor doof bzgl. 2erPotenzDivisionen. Mit dem alten AVR-GCC waren aber beim selben Prog ohne Superoptionen noch 20 Byte frei ;-) Man fühlt sich wie bei den über die Jahre immer langsamer werdenden Windows-OS trotz immer stärkerer CPUs ;-) D.h. erste Frage bleibt erstmal, was denn der neue GCC seit 2007 eigentlich verbockt und verplempert !? back to the roots? Ich hoffe der alte GCC versteht auch einige der Superoptionen. Und ob das Program überhaupt noch läuft mit den gefährlichen Sachen wird sich zeigen... (Vermutlich wird also ohne steiniges Assembler nicht viel gehen. Funktionaltät und Datentypen wurden schon recht aggressiv verschlankt - oft auf das .s geguckt. Ich will v.a. 512 Byte für den fastbootloader noch freikriegen und dann nochmal ca. 500 Byte für Funktionen, also 1k = 12% )
Wegen deiner Division. Schau mal in den Code, ob du tatsächlich unsigned Variablen benutzt hast. Bei signed Variablen ist Schieben (bzw wie in diesem Fall ein Byte weglassen) eben nicht identisch zur Division und für den gcc daher ein Grund, eine Division einzusetzen.
Das hier bringt noch ein paar interessante Feinheiten (Compiler-Schalter und Codeorganisation) dazu http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Optimierung_der_Gr.C3.B6.C3.9Fe Beitrag "Tricks, wie ich mein Compilat kleiner kriege" das meiste wie hier hatte ich schon selbstverständlich gemacht. Der Autor kam aber auch schon damals (2007) zu dem Schluß daß ein älterer Compiler kleineren Code produziert. Es war also tatsächlich schon immer so dass es mit höheren Versionen immer schlechter wurde ;-) Ich muß also wohl eher noch ältere Compiler ansehen als neuere... Ich werde wohl v.a. noch die Datentypen im Prog für -mint8 umschreiben, das sollte noch 200Byte bringen, wie ein grobes Checken des .s und Glaskugel mir sagt.
Karl Heinz Buchegger schrieb: > Wegen deiner Division. > Schau mal in den Code, ob du tatsächlich unsigned Variablen benutzt > hast. Bei signed Variablen ist Schieben (bzw wie in diesem Fall ein Byte > weglassen) eben nicht identisch zur Division und für den gcc daher ein > Grund, eine Division einzusetzen. in dem einen Fall den ich jetzt zur Prüfung betrachtet habe ist tatsächlich ein signed int reingegangen. ab -O1 machte ers dennoch ohne __divXX Funktion. In der Tat macht er jetzt bei -Os mit unsigned cast an der Stelle hier einfach den Registershift - auch sonst überall mit 2erPotenzDivs wo (meist) eh unsigned war. Ich war mir recht sicher daß der alte GCC mit -Os auch bei unsigned rcall __udivXX in absurder size-optimierungs Weise machte - und deshalb wegen häufiger Verwendung (für pseudo-Fixpoint Berechnungen) nicht verwendbar war. (evtl. eine Wechselwirkung mit den neuen inline-cost Optionen ... Inflation von Fragen ..) muß nochmal genau nachschauen. Evtl. kann ich hier tatsächlich mit -Os leben. die wenigen nötigen signed div 2**N kann ich extra behandeln. Dennoch bleibt die Frage warum die neueren GCC offensichtlich bei gleichen Option immer speicherverschwenderischer werden?
kxr schrieb: > Dennoch bleibt die Frage warum die neueren GCC offensichtlich bei > gleichen Option immer speicherverschwenderischer werden? Weil der GCC in erster Linie ein Compiler für große Programme auf großen Prozessoren ist. Und Optimierungsstrategien, die dort Vorteile bringen, können am anderen Ende der Skala (Miniprogramme auf 8-Bitter) eben kontraproduktiv sein.
kxr schrieb: > Frage: Was ist der Hauptgrund warum der neue AVR-GCC scheinbar ohne > Gegenleistung soviel mehr Speicher statt weniger frißt? Was kann man tun Bei der avr-gcc-Entwicklung mithelfen. > Per anschauen des generierten Assembler-Code sehe ich auf Anhieb vieles > was ein "perfekter" Compiler viel schneller UND kürzen machen > könnte...!? Auch einer, der den C-Standard befolgt? ;-) Gibt es Beispiele? > Was sind typisch die besten Methoden um Prog-Speicher zu sparen - mit > minimax bzgl. Schreib- & Fuzzel-Denkaufwand ? Mein Favorit ist avr-gcc 3.4.6, bei meinen Programmen hat der noch jede 4-er Version geschlagen.
@ kxr (Gast) >(Vermutlich wird also ohne steiniges Assembler nicht viel gehen. >Funktionaltät und Datentypen wurden schon recht aggressiv verschlankt - >oft auf das .s geguckt. Ich will v.a. 512 Byte für den fastbootloader >noch freikriegen und dann nochmal ca. 500 Byte für Funktionen, also 1k = >12% ) Wenn gleich ich der Letzte bin, der hirnloses Verballern von Resourcen jeglicher Art propagieren würde, so sollte man auch hier mal tief durchatmen und erstmal einen Schritt zurück machen. "Ich habe ein Programm für 8k (ATMega 8/28pin) welches jetzt knapp den ganzen Speicher belegt - noch 20 Byte frei. Bisher WinAVR/AVR-GCC von 2007." "Jetzt soll aber noch etwas Funktionalität rein/verbessert werden (ohne zuviel Kopfzerbrechen und Assembler)" Dazu nimmt man einen pin- und funktionskompatiblen ATmega168, der hat 16k Flash und fertig. Wenn man nicht 1e6 Stück davon im Jahr verbauen will, ist der Preisaufschlag zu verkraften. Oder nicht? AVR-GCC-Codeoptimierung http://blogs.msdn.com/b/audiofool/archive/2007/06/14/the-rules-of-code-optimization.aspx MfG Falk
Johann L. schrieb: > Mein Favorit ist avr-gcc 3.4.6, bei meinen Programmen hat der noch jede > 4-er Version geschlagen. Versionsvergleich: Flash-Bedarf (__data_load_end) mit den Ur-Optionen -g -Wall -O1 bzw -g -Wall -Os Version ; -O1; -Os winavr-20060421/gcc3.4.6 0x00002180 0x00001f20 winavr-20070525/gcc4.1.2 0x00001fe2 0x00001e4e winavr-20071221/gcc4.2.2 0x00001f9a 0x00001e10 winavr-20100110/gcc4.3.3 0x00002198 0x00001fa8 d.h. winavr-20071221/gcc4.2.2 (eins höher als mein bisheriger) ist dann Favorit. das Übel kam mit gcc 4.3.
Hier mal meine Compile.bat:
1 | @echo off |
2 | |
3 | set mcu=mega48 |
4 | |
5 | set main=knightrider |
6 | |
7 | set ac=c:/avr/winavr |
8 | |
9 | echo --std=gnu99 >opt.z |
10 | echo -lm >>opt.z |
11 | echo -fno-inline-small-functions >>opt.z |
12 | :echo -fno-tree-scev-cprop >>opt.z |
13 | :echo -fno-split-wide-types >>opt.z |
14 | :echo -fno-tree-loop-optimize >>opt.z |
15 | echo -fno-move-loop-invariants >>opt.z |
16 | echo -Wl,--relax >>opt.z |
17 | echo --combine -fwhole-program >>opt.z |
18 | |
19 | path %ac%\bin;%path% |
20 | avr-gcc.exe -dumpversion |
21 | avr-gcc.exe -xc *.c @opt.z -Os -mmcu=at%mcu% -Wall -gdwarf-2 -Wl,-Map=main.map -o %main%.out |
22 | ::avr-gcc.exe -xc -E -dM -mmcu=at%mcu% main.c >macro.lst |
23 | if errorlevel 1 goto end |
24 | |
25 | cmd /c avr-objdump.exe -h -S %main%.out >%main%.lst |
26 | cmd /c avr-objcopy.exe -O ihex %main%.out %main%.hex |
27 | avr-size.exe --format=avr --mcu=at%mcu% %main%.out |
28 | del %main%.out |
29 | del opt.z |
30 | :"C:\Programme\Atmel\AVR Tools\STK500\stk500.exe" -dAT%mcu% -e -pf -vf -if%main%.hex |
31 | :call fboot -b38400 -ptest.hex -vtest.hex |
32 | :end |
33 | pause |
Der Schalter "-fno-inline-small-functions" bringt immer deutlich was. Agressiv optimiert der "--combine -fwhole-program", aber er bestraft volatile-Fehler. Mit dem ":" kann man bequem Schalter auskommentieren. Peter
kxr schrieb: > d.h. winavr-20071221/gcc4.2.2 (eins höher als mein bisheriger) ist dann > Favorit. das Übel kam mit gcc 4.3. Mit dem alten GCC 4.2.x (bei mir 4.2.4) bin ich auch ganz glücklich. Die neueren Versionen nehme ich meistens nur für neuere Controller, die von 4.2.x noch nicht unterstützt werden. Man kann aber nicht generell sagen, dass sich die neueren Versionen verschlechtert haben. Insbesondere bei der 8-Bit-Arithmetik hat sich einiges getan. Wo 4.2 noch oft unnötigerweise explizite int-Propagations gemacht hat, konnte man bei 4.3 schon gewisse Verbesserungen erkennen, und 4.4 war diesbezüglich schon richtig gut. Da aber im Zuge der Verbesserungen einige andere Dinge auf der Strecke blieben, hängt das Ergebnis sehr stark von den Eigenschaften des kompi- lierten Codes ab. Ich habe deswegen immer die aktuellsten Versionen der 4.2.x-, 4.3.x- , 4.4.x-, 4.5.x- und 4.6.x-Serien installiert, damit ich im Zweifelsfall alle nacheinander durchprobieren kann.
kxr schrieb: > Johann L. schrieb: >> Mein Favorit ist avr-gcc 3.4.6, bei meinen Programmen hat der noch jede >> 4-er Version geschlagen. > > Versionsvergleich: > > Flash-Bedarf (__data_load_end) mit den Ur-Optionen -g -Wall -O1 bzw -g > -Wall -Os > > Version ; -O1; -Os > winavr-20060421/gcc3.4.6 0x00002180 0x00001f20 > winavr-20070525/gcc4.1.2 0x00001fe2 0x00001e4e > winavr-20071221/gcc4.2.2 0x00001f9a 0x00001e10 > winavr-20100110/gcc4.3.3 0x00002198 0x00001fa8 Kommt natürlich auch darauf an, wie man den Code hinschreibt...
Johann L. schrieb: > Kommt natürlich auch darauf an, wie man den Code hinschreibt... Hmmm.... beschreibe doch bitte was du genau meinst.
wenn ich -Os benutze, aber gezielt einzelne signed div 2**N schnell haben will, wie kriege ich dann sowas movw r24,r16 ; delta_speed, delta_speed ldi r22,lo8(2) ; , ldi r23,hi8(2) ; , rcall __divmodhi4 am einfachsten/elegantesten auf schnellen -O1 Stil für ein paar Byte mehr: movw r18,r14 ; delta_speed, delta_speed tst r15 ; delta_speed brge .L206 ; , subi r18,lo8(-(1)) ; delta_speed, sbci r19,hi8(-(1)) ; delta_speed, .LVL136: .L206: asr r19 ; tmp268 ror r18 ; tmp268 (d.h. notfalls in händischer C oder C-asm notation? oder pragma...? es reichte auch der nakte ASR mechanismus (-1 / 2 = -1) , d.h. das bedingte +1 ist egal.) Bzw. da -Os und -O1 ja Sammlungen detaillierter Optionen sind, welche sind für diese signed div 2**N verantwortlich?
Wenn du schnellen Code willst, verwende z.B. -O2 Für die Optionen, die -O* nach sich zieht, verwende -save-temps -fverbose-asm ohne -pipe und schau ins s-File.
Johann L. schrieb: > Wenn du schnellen Code willst, verwende z.B. -O2 > Für die Optionen, die -O* nach sich zieht, verwende -save-temps > -fverbose-asm ohne -pipe und schau ins s-File. ich will ja eben nicht mit -O2 riesigen Speicher verbrennen. hatte ja vorher -O1, was auch die signed div nach Wunsch macht. Nun aber -Os insgesamt wichtiger. Aber eben an ein paar kritischen Stellen wo er signed div 2**N mit der elend langsamen Routine, soll eine Ausnahme her - einigermaßen elegant geschrieben.
kxr schrieb: > es reichte auch der nakte ASR mechanismus (-1 / 2 = -1) , d.h. das > bedingte +1 ist egal. Wenn dir das egal ist, kannst du ja den >>-Operator nehmen.
Yalu X. schrieb: > > Wenn dir das egal ist, kannst du ja den >>-Operator nehmen. der macht eben kein signed (asm ASR xxx). wie schreibe ich signed div 2 in C / GCC-asm hin? oder am besten: wie übersteuere ich -Os so daß aber signed div doch optimiert werden. (soweit würde es pauschal passend) oder: wie schalte ich Abschnittsweise auf -O1 o.ä. ?
kxr schrieb: >> Wenn dir das egal ist, kannst du ja den >>-Operator nehmen. > > der macht eben kein signed (asm ASR xxx). Bei mir schon:
1 | int16_t halbiere(int16_t x) { |
2 | return x >> 1; |
3 | }
|
GCC 4.2.4 mit -Os macht daraus
1 | halbiere: |
2 | asr r25 |
3 | ror r24 |
4 | ret |
Yalu X. schrieb: > Bei mir schon: in der Tat - im 4.2.2 auch. Ich hatte C's >> allgemein als reinen bitshift erwartet und nicht im entferntesten ASR erwartet. http://en.wikipedia.org/wiki/Arithmetic_shift The (1999) ISO standard for the C programming language defines the C language's right shift operator in terms of divisions by powers of 2. Because of the aforementioned non-equivalence, the standard explicitly excludes from that definition the right shifts of signed numbers that have negative values. It doesn't specify the behaviour of the right shift operator in such circumstances, but instead requires each individual C compiler to specify the behaviour of shifting negative values right. 3 ^ The >> operator in C and C++ is not necessarily an arithmetic shift for signed integers. The C99 standard specifies that the resulting value is implementation-defined for a right shift in which the left operand is a signed integer that is negative. However, most implementations use sign extension, thereby making the >> operator an arithmetic shift. For instance, the GCC is such an implementation.
Ja, 100%ig portabel ist der Shift von signed Werten nicht. Du kannst aber davon ausgehen, dass jeder C-Compiler, dessen Zielsystem über einen ASR-Befehl verfügt, diesen für signed Shifts auch nutzt. Und ich kenne keinen aktuellen Prozessor, der kein ASR kann. Ein Workaround mit Inline-Assembler wäre auf jeden Fall weniger portabel.
Falk Brunner schrieb: > Der PIC C18 Compiler kann KEINE arithmetischen right shifts! Interessant. Hab gerade mal nach geschaut: Die 12-, 14- und 16-Bit-PICs scheinen gar keine Shift- sondern nur Rotate-Befehle zu haben. Damit lässt sich ein arithmetischer Shift tatsächlich nicht so leicht realisieren. Aber auch einen unsigned Shift gibt es damit nicht out-of-the-box, weil man sich dabei immer noch um das Carry-Flag kümmern muss. <flame> Also sind die AVRs doch besser als die PICs </flame> ;-)
Yalu X. schrieb: > <flame> > Also sind die AVRs doch besser als die PICs > </flame> ;-) Programmiermäßig sowieso. letztens hatte ich einen ATmega8 DIL falschrum in den Sockel. die Pin-Belegung von GND/VCC/AGND... ist so durchdacht dass er das überlebt. die anderen Pins sind kurzschlussfest. gedreht und läuft einfach weiter ...
Bei den AVRs is nix kurzschlussfest! Haste eben Glück gehabt. Elektronik ist heutzutage sowieso ziemlich robust geworden.
Habe auch schon etliche Mega32@5V verpolt eingesetzt. Bisher keiner defekt.
> Also sind die AVRs doch besser als die PICs Waren Sie noch nie, sind sie nicht und werden sie auch nie sein. AVR sind eher was für Bastler & Amateure, während PIC für den professinelen Einsatz gedacht sind. Das sieht man auch an der Palette der Bausteine - wie z. B. der DSPic Serie. Da hat Atmel nichtsentgegenzusetzen. PIC for ever.
Marco S. schrieb: > Habe auch schon etliche Mega32@5V verpolt eingesetzt. Bisher keiner > defekt. hat das selbe Prinzip der GND-Drehung beim DIL-Sockel. man hat an die Entwickler gedacht. Hans schrieb: > Das sieht man auch an der Palette der Bausteine - > wie z. B. der DSPic Serie. dsp PICs haben mit den normalen PICs eigentlich nix zu tun. Wünsche aber fröhliches Bank-Switching ;-) (hier nie mehr wieder). Für große Stückzahlen, Assembler-Schlachten und Cents einsparen würde ich eher zu den neuen STM8 oder chinesischen Teilen gehen. zurück: Falls jemand mal die spezial-Option findet/auffällt welche im Rahmen von -Os den Arithmetik-Shift bei signed div 2**N wieder aktiviert ... bitte hier posten.
kxr schrieb: > Falls jemand mal die spezial-Option findet/auffällt welche im > Rahmen von -Os den Arithmetik-Shift bei signed div 2**N wieder > aktiviert ... bitte hier posten. Solch eine Option gibt es nicht. Eine Division mit -Os kostst 3 Instruktionen: Laden der Konstante (2) und Aufruf der Divisionsroutine (1). Ein Shift ist länger, selbst wenn nur durch 2 dividiert wird. Die Kosten für die Divisionsfunktion entstehen nur ein einziges Mal. Oder du machst es händisch:
1 | static inline int div_p2 (int a, unsigned int shift) |
2 | { |
3 | if (a < 0) |
4 | a += (1 << shift) - 1; |
5 | |
6 | return a >>= shift; |
7 | } |
>> dsp PICs haben mit den normalen PICs eigentlich nix zu tun.
Was soll der Unfug? Das ist doch kein Argument!
Hans schrieb: >>> dsp PICs haben mit den normalen PICs eigentlich nix zu tun. > > Was soll der Unfug? Das ist doch kein Argument! Ist kein Unfug. Ein SAM3S16C hat mit nem AVR auch nichts zu tun, sind aber beide von Atmel. Peter
Hans schrieb: > PIC for ever. Da schimmert deutlich der Pragmatismus des erfahrenen Ingenieurs durch ;-)
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.