Ich unterteile ja gern meinen Code in kleine Funktionen.
Das Problem ist aber, daß der SRAM-Verbrauch im AVR dann sehr stark
ansteigt.
Der GCC scheint ziemlich feste Ansichten zu haben, welche Register was
machen, obwohl die ja fast universell austauschbar sind. Außerdem geht
er immer davon aus, daß jede Funktion alle "Call-used registers"
zerstört.
Eine Registeroptimierung scheint nicht stattzufinden und er PUSH, POP,
MOVW in jeder Funktion sehr viel unnütz.
Ich habe z.B. das Problem, daß bei nur 2kB Code schon 70Byte SRAM nur
fürs (unnütze) Register retten draufgeht, ist bei 256 Byte SRAM ne ganze
Menge.
Ich hab nun versucht, mit "-fwhole-program" die Call-Barrieren
aufzulösen, um den SRAM-Bedarf zu optimieren.
Es geht allerdings nur, wenn ich alle C-Files in ein einziges
includiere.
Ansonsten kriege ich Fehlermeldungen, z.B.:
Komischer Weise hat das aber auch keine RAM-Einsparung gebracht, da er
plötzlich im Main erstmal etwa 20 Register PUSHt.
Mit "__attribute__((noreturn))" kriege ich die 20 Bytes zwar wieder
frei, aber dann warnt er blöd rum. Kann man die Warnung abschalten?
Ich muß wohl dem GCC zuliebe das modulare Programmieren aufgeben und
alles in einen einzigen langen Spaghetticode pappen.
Hat vielleicht jemand gute Tips bezüglich SRAM/Registeroptimierung?
Gibts da vielleicht irgendwelche geheimen Schalter?
Peter
P.S.:
Ich benutze den aktuellen WINAVR GCC 4.3.2
Peter Dannegger wrote:
> Mit "__attribute__((noreturn))" kriege ich die 20 Bytes zwar wieder> frei, aber dann warnt er blöd rum. Kann man die Warnung abschalten?
Schon mit dem Attribut "OS_main" statt "noreturn" versucht?
Bei wirklich kleinen Funktionen wäre es vielleicht auch eine Überlegung
wert, sie als static inline in ein Header-File zu packen.
Man sollte sich nur klar darüber sein, dass man dem Compiler mit
-ffreestanding auch einige Optimierungsmöglichkeiten nimmt. Das
klassische Beispiel ist ein strlen("..."), das dann nicht mehr durch
eine Konstante ersetzt werden kann.
Stefan Ernst wrote:
> Das> klassische Beispiel ist ein strlen("..."), das dann nicht mehr durch> eine Konstante ersetzt werden kann.
Meinst Du, wenn man statt strlen auch sizeof nehmen könnte?
Peter
Peter Dannegger wrote:
> Meinst Du, wenn man statt strlen auch sizeof nehmen könnte?
Klar kann man auch sizeof verwenden, wenn du entsprechend
berücksichtigst, dass sizeof einen anderen Wert liefert.
Selbstverständlich könnte man auch gleich eine Zahl hinschreiben.
Ernst Bachmann wrote:
> Gibts eine Möglichkeit dem GCC das Meckern wg "void main(void)"> schonender abzugewöhnen?
Ich sehe die Notwendigkeit nicht. Ein Standard-main zusammen mit dem
Attribut OS_main sollte doch das Gewünschte liefern.
Peter Dannegger wrote:
> Der GCC scheint ziemlich feste Ansichten zu haben, welche Register was> machen, obwohl die ja fast universell austauschbar sind. Außerdem geht> er immer davon aus, daß jede Funktion alle "Call-used registers"> zerstört.
Klar. Die hat jeder Compiler und muss sie haben. Das heißt ABI.
Eine Funktion ist eine Black Box. Der Compiler kennt den Code ja nicht
und muss davon ausgehen, daß die Register zerstört werden. Dem
entsprechend muß er, wenn lokale Variablen einen Call überleben müssen,
diese sichern. Das kann nur dadurch geschehen, indem sie in
call-saved-regs gesichert werden, aber deren Inhalt muss ihrerseit
gesichert werden, nämlich auf dem Stack. ALternativ können Werte direkt
auf dem Stack gesichert werden, oder eine Variable wird erst garnicht in
einem GPR angelegt, sondern direkt im Frame der Funktion.
Und bestimmte Register haben bestimmte Funktionen. Adress-Register sind
sehr explusiv, im Endeffekt bleibt bei komplexen Funktionen nur Z (Y ist
framepointer) und X kann keine Offsets adressieren.
Ebenso muss natürlich festgelegt sein, in welchen Registern eine
Funktion ihre Argumente erhält und wo der Return-Wert zu liefern ist.
> Eine Registeroptimierung scheint nicht stattzufinden und er PUSH, POP,> MOVW in jeder Funktion sehr viel unnütz.
Doch, schon. Aber je komplexer eine Funktion ist, desto mehr muss
gesichert werden. Unangenehm können da Optimierungen wie constant
propagation oder invariant motion wirken.
> Ich hab nun versucht, mit "-fwhole-program" die Call-Barrieren> aufzulösen, um den SRAM-Bedarf zu optimieren.> Es geht allerdings nur, wenn ich alle C-Files in ein einziges> includiere.> Ansonsten kriege ich Fehlermeldungen, z.B.:>
Es sind alle Quellen anzugeben. Offenbar enthält main.c externe
Referenzen, zB für checker.
> Ich muß wohl dem GCC zuliebe das modulare Programmieren aufgeben und> alles in einen einzigen langen Spaghetticode pappen.>> Hat vielleicht jemand gute Tips bezüglich SRAM/Registeroptimierung?> Gibts da vielleicht irgendwelche geheimen Schalter?
Du kannst Funktionen inlinen, was zu einem stark wachsenden Code führen
dürfte. Einen Tod musst du dann sterben,...
Was sinnvoll ist zum Inlinen musst du erst mal rausfinden. Funktionen
die zB nur triviale Sachen machen wie set_flag(char f) {flag=f;}
bringen natürlich einen ziemlichen Overhead, wenn sie nicht geinlint
werden, man muss sich also überlegen welche Granulierung in Funktionen
überhaupt sinnvoll ist. Eine Aufruf foo() kostet immer gleich viel an
Code, egal wie komplex foo intern ist, denn der Compiler kennt den Code
nicht. U.U existiert der Code nocht nichtmal und das Modul, das foo
implementiert, wird erst später erstellt.
Funktionen sollten auch so geschrieben werden, daß möglichst kein Frame
gebraucht wird. Für komplexe Funktionen wird der natürlich gebraucht,
aber für einfache Funktionen gibt's ein paar Dinge, die man unbedingt
meiden sollte. Ohne die (präcompilierte) Quelle zu sehen, kann man da
aber nix weiter zu sagen.
Johann
Zu den Frames gibt schon einen Tipp:
Wenn man von einer lokalen Variablen die Adresse verwendet, und die ist
nicht "static", dann hat man einen kompletten Stackframe gewonnen. Und
da Atmel sich auch noch bei den xmegas hartnäckig weigert, einen Befehl
zur atomaren Manipulation des Stackpointers zu definieren, ist der Frame
etwas aufwendig (von IAR geschmiert? wäre ja nicht schwierig gewesen).
Das betrifft einerseits lokale Arrays wie Stringpuffer. Macht man die
"static", geht das nur auf andere Weise zu Lasten des RAM-Verbrauchs.
Man kann auch die Puffer mehrerer solcher Funktionen zusammenfassen,
aber die programmtechnische Eleganz (Kapselung) lässt dann etwas nach.
Lokale "struct"s sind natürlich auch solche Kandidaten, aber das ist in
der Praxis bei Programmierung kleiner µCs eher bei C++ als bei C ein
Thema.
Vor allem aber bedeutet es, soweit wie möglich auf Rückgabewerte per
Pointer zu verzichten. Wer beispielsweise eine Funktion zur
Temperaturmessung so schreibt, dass der Returnwert nur OK/Fehler anzeigt
und der Temperaturwert über einen Adressparameter zurückgegeben wird,
der hat schon im Ansatz verloren. Hier geht diese Strategie also schon
sehr früh ins Programmdesign ein. Effizienter ist es hier, für "Fehler"
nicht vorkommende Temperaturwerte zu missbrauchen (wie -300°C).
Johanns skizzierte "set_flag" Funktion ist jedoch vergleichsweise
harmlos. Einen Frame kriegt man dabei keinen, nur die volatilen Register
sind seitens des Callers natürlich weg.
Lange push/pop-Orgien verkürzt man mit -mcall-prologues. Braucht nicht
weniger RAM, sieht aber im Listing nicht so krass aus.
Ein Frame wird billiger, indem man den Stack auf eine 256-Byte Seite
einschränkt: -mtiny-stack. Dabei sollte man dann aber beachten, dass je
nach Controller der Stack nicht bei xxFF anfängt, sondern woanders.
A. K. wrote:
> Zu den Frames gibt schon einen Tipp:>> Wenn man von einer lokalen Variablen die Adresse verwendet, und die ist> nicht "static", dann hat man unweigerlich einen kompletten Stackframe> gewonnen.
Gleiches gilt für Übergabeparameter. Von einem Register kann keine
Adresse genommen werden. Selbst wenn ein µC das bietet (AVR ist ein
Fall) wird das vom Compiler nicht unterstützt, und es ist auch nicht
sinnvoll, das zu unterstützen.
> Und da Atmel sich hartnäckig weigert, einen Befehl zur> atomaren Manipulation des Stackpointers zu definieren, ist der Frame> etwas aufwendig.
Ja sowas würde man sich natürlich wünschen, den SP atomar und einfach in
einer Instruktion verschiebbar zu machen. Platz in den Opcodes wäre da.
> Das betrifft einerseits lokale Arrays wie Stringpuffer. Wenn's geht> macht man die "static", was aber zu Lasten des RAM-Verbrauchs geht. Man> kann auch die Puffer mehrerer solcher Funktionen zusammenfassen, aber> die programmtechnische Eleganz (Kapselung) lässt dann etwas nach.> Lokale "struct"s sind natürlich auch solche Kandidaten, aber das ist in> der Praxis bei Programmierung kleiner µCs eher bei C++ als bei C ein> Thema.
Ich arbeite recht viel mit Structs/Unions in C, auch zur
Parameterübergabe und als lokale Variablen. Zu beachten ist aber, daß
eine solche nur in einem Register angelegt werden kann, wenn sie eine
gerade Größe hat, also 1, 2 oder 4 Bytes. Zu einer Struktur, die
RGB-Farbwerte enthält, kann man also gewinnbringend ein Dummy-Feld
hinzufügen (falls man keine großen Arrays darauf aufbaut).
> Vor allem aber bedeutet es, soweit wie möglich auf Rückgabewerte per> Pointer zu verzichten. Wer beispielsweise eine Funktionen zur> Temperaturmessung so schreibt, dass der Returnwert nur OK/Fehler anzeigt> und der Temperaturwert über einen Adressparameter zurückgegeben wird,> der hat schon im Ansatz verloren.
Du meinst sowas?
1
intfoo(int*);
Schlimm ist das ja nicht, auch wenn es per Return günstiger wird. Übel
wir nur die Call-Seite, wenn eine lokale Variable referenziert wird:
1
voidbar(intx)
2
{
3
foo(&x);
4
}
Wenn man hingegen Adressen von globalenb Variablen transportiert, kann
so ein Ansatz zu einem deutlichen Code-Schrumpf führen.
> Lnge push/pop-Orgien verkürzt man mit -mcall-prologues.
Das macht den Code u.U kleiner. Aber spart das RAM? Seh ich jetzt nicht.
Weniger als die notwendigen REGs wird gcc nicht sichern. U.U wird er
aber mehr sichern als notwendig, wenn die zu sichernde reg-sequence ein
Loch hat.
Johann
> Schlimm ist das ja nicht, auch wenn es per Return günstiger wird.
Für diese Funktion selbst nicht. Nur wird in 90% der Fälle der Caller
eine lokale Variable verwenden, und dann ist's aus. Da ist es
sinnvoller, sowas gleich vorneweg im Design zu vermeiden, statt sich
hinterher beim Aufruf einen abbiegen zu müssen.
Das heisst beispeilsweise auch, dass es bei Dingern wie den
Temp/Feuchte-Sensoren SHT11 sinnvoller sein kann, getrennte Funktionen
für Temperatur und Feuchte zu implementieren. Das zusammenzufassen
bringt bei AVRs eher Nachteile.
> Das macht den Code u.U kleiner. Aber spart das RAM?
Nö, aber passte da grad rein ;-). Ist mehr Psychologie, jene
adressierend, denen schon beim Anblick der push/pop-Orgien schlecht
wird.
A. K. wrote:
> Johann L. wrote:>>> Du meinst sowas?>>
1
>>intfoo(int*);
2
>>
>> Schlimm ist das ja nicht, auch wenn es per Return günstiger wird.>> Für diese Funktion selbst nicht. Nur wird in 90% der Fälle der Caller> eine lokale Variable verwenden, ...
Wie gesagt, ich verwende es mit globalen Variablen um Codegröße zu
sparen. Allerding nicht mit int als Basistyp. Aber das führt jetzt zu
weit, weil das Code spart, aber kein RAM (und wenn, dann eher als
angenehmer Nebeneffekt).
Johann
Peter Dannegger wrote:
> Ich unterteile ja gern meinen Code in kleine Funktionen.
Die Frage ist auch, was "klein" ist, und in welchen Kontext das dann
aufgerufen wird.
> Komischer Weise hat das aber auch keine RAM-Einsparung gebracht, da er> plötzlich im Main erstmal etwa 20 Register PUSHt.> Mit "__attribute__((noreturn))" kriege ich die 20 Bytes zwar wieder> frei, aber dann warnt er blöd rum. Kann man die Warnung abschalten?
Ich lasse main wie es ist und mache die Hauptschleife in einer
noreturn-Funktion. Die main-Loop und den ganzen Kladderatddatsch will
man eh nicht in main haben. Das initialisiert die Hardware und ab geht's
in die Hauptschleifen-Func.
> Hat vielleicht jemand gute Tips bezüglich SRAM/Registeroptimierung?> Gibts da vielleicht irgendwelche geheimen Schalter?
"Geheim" sind -morder1 und -morder2, die Einfluß auf die
Register-Allokierung haben.
Mit -morder1 wird der Code manchmal kleiner, manchmal nicht. Das für
jede Funktion/Modul auszuwünscheln hab ich mir nicht den Wolf gemacht.
Nur einmal den Build-Prozess abhängig vom der Modulgröße gemacht: Für
einen bestimmten Satz von Optionen jeweils alle Object-Varianten
auscompiliert und das kleinste Modul gelinkt. Das ist eine Fingerübung
in make.
Inwieweit eine andere regalloc-order in der Lage ist, die Anzahl zu
sichernder Register zu reduzieren, hab ich nicht untersucht. Ich würd
aber mal schätzen, daß es kaum Einfluß hat, weil es die gleiche ABI
implementiert. Und diese Option ist wie gesagt undokumentiert -- sie
diente den Entwicklern des avr-Backend in der Entwicklungsphase zum
Eruieren einer guten regalloc-order und ist im Backend drinne, weil es
(noch) niemand entfernt hat.
Mit -morder2 hab ich noch nie kleineren Code gesehen.
Prinzipiell wäre es möglich, für einfache Funktionen ein anderes
call-Interface zu implementieren, indem man eine Funktion, von der man
weiß (oder hofft), daß sie nur simple Sachen macht und nach Möglichkeit
eine leaf-Funktion ist, per __attribute__((simple)) kennzeichnet.
Für eine Funktion kann der Compiler zwar erkennen, daß sie "simpel" ist
(wie immer man das definiert), der Caller muss aber zu seiner
Compilezeit wissen, daß der Callee "simpel" ist! Daher müsste man ein
solches Attribut einführen, um das dem Caller per Prototyp des Callee
mitzuteilen. Falsche Attribuierung führt dann übrigens klar zum Crash
(wie es i.d.R eben ist bei falschen Prototypen).
Johann
A. K. wrote:
> Und> da Atmel sich auch noch bei den xmegas hartnäckig weigert, einen Befehl> zur atomaren Manipulation des Stackpointers zu definieren, ist der Frame> etwas aufwendig (von IAR geschmiert? wäre ja nicht schwierig gewesen).
Naja, mit seinerzeit IAR als einzigem Berater ist keiner auf die
Idee gekommen, dass man statt zwei Stacks (von denen ja einer immer
präalloziert sein muss) auch alles in einem Stack unterbringen kann,
würde ich mal sagen. Wenn man zeitlebens nur mit zwei Stacks gearbeitet
hat und nie ein Unix gesehen hat (die ja praktisch alle einen Stack
benutzen), dann kommt man möglicherweise nicht auf die Idee...
Mittlerweile gibt's da im Prolog teilweise "rcall .". Ich hatte
zuerst an einen Bug geglaubt... ist aber weiter nichts, als auf dem
Stack zwei (bzw. drei) Bytes frei zu räumen. :-o
@Stefan,
> Schon mit dem Attribut "OS_main" statt "noreturn" versucht?
Ja, das hats gebracht.
Keine PUSHs und keine Warnung, super danke.
> Bei wirklich kleinen Funktionen wäre es vielleicht auch eine Überlegung> wert, sie als static inline in ein Header-File zu packen.
Ja, ich hab jetzt mal die Module etwas größer gemacht (z.B. I2C und
EEPROM zusammen), und dann die Unterfunktionen always inline.
Hat etwa 15 PUSHs eleminiert und 4 Calls.
@Johann
> Klar. Die hat jeder Compiler und muss sie haben. Das heißt ABI.> Eine Funktion ist eine Black Box. Der Compiler kennt den Code ja nicht> und muss davon ausgehen, daß die Register zerstört werden.
Z.B. der Keil C51 erstellt sich ein Registerfile, wo die verwendeten
Register einer Funktion mit einem Flag gekennzeichnet ist.
Beim 2. Durchlauf kann er dann die Aufrufer entsprechend optimieren,
bzw. noch weitere Durchläufe machen.
Bzw. wenn die aufgerufenen Funktionen vor dem Aufrufer stehen, kennt er
diese ja schon und optimiert natürlich gleich im 1.Durchlauf.
Diese Optimierung müßte eigentlich auch der GCC können.
>> F:\WORK\AVR_C\AUTORANG/main.c:52: undefined reference to `checker'>> MAKE.EXE: *** [main1.elf] Error 1> Es sind alle Quellen anzugeben. Offenbar enthält main.c externe> Referenzen, zB für checker.
Dann dürfte es ja ohne "-fwhole-program" auch nicht gehen, da geht es
aber.
Ich muß auch nicht sämtliche Objekte includieren, einige können durchaus
separat compiliert werden.
Ich werd mal nen Beispielcode zusammenstellen und posten.
Peter
Peter Dannegger wrote:
> Ja, ich hab jetzt mal die Module etwas größer gemacht (z.B. I2C und> EEPROM zusammen), und dann die Unterfunktionen always inline.> Hat etwa 15 PUSHs eleminiert und 4 Calls.
Nur so als Hinweis:
Das Zusammenfassen zu größeren Modulen ist nicht unbedingt nötig. Du
kannst die gemeinsamen Unterfunktionen auch in eine separate Datei
auslagern und dann in beide Module inkludieren. Du darfst halt nur nicht
vergessen, diese Unterfunktionen dann als static zu deklarieren.
Peter Dannegger wrote:
>>> F:\WORK\AVR_C\AUTORANG/main.c:52: undefined reference to `checker'>>> MAKE.EXE: *** [main1.elf] Error 1>> Es sind alle Quellen anzugeben. Offenbar enthält main.c externe>> Referenzen, zB für checker.>> Dann dürfte es ja ohne "-fwhole-program" auch nicht gehen, da geht es> aber.
Natürlich kannst du ohne -fwhole-program externe Referenzen nutzen, ohne
daß es zu Problemen im Compiler kommt. Diese werden erst vom Linker
aufgelöst.
Aber wenn du über den Schalter sagst, "hey, hier ist das komplette
Programm" und dann fehlt was, wird eben gemeckert.
Ebenso würde bei unauflösbaren externen Referenzen gemeckert werden,
wenn diese ohne -fwhole-program beim Linken/Lokatieren auftreten würden.
Johann