Forum: Compiler & IDEs static Funktionen und Flashverbrauch


von Ernie (Gast)


Lesenswert?

Hallo Gemeinde,

wenn ich meine Funktionen im Programm als static deklariere, sinkt der 
Flashverbrauch (wahrscheinlich, weil keine Linkerinformationen 
mitgegeben werden oder so??).
Wenn ich nun aber "ordentlich" mein Programm auf mehrere Files (mit 
passenden Headerdateien) aufteilen will, geht "static" ja nicht, und der 
Flashverbrauch steigt.

Kann man das irgendwie umgehen (also ordentliche Dateistruktur + kleiner 
Flashverbrauch) z.B. mit einer Linkeroption?

Lg,

Berts Kumpel

von Karl H. (kbuchegg)


Lesenswert?

Ernie schrieb:
> Hallo Gemeinde,
>
> wenn ich meine Funktionen im Programm als static deklariere, sinkt der
> Flashverbrauch (wahrscheinlich, weil keine Linkerinformationen
> mitgegeben werden oder so??).

Nein.
Sehr viel wahrscheinlicher, weil der Compiler die Funktion inlinen wird. 
(inlinen = den Funktionsaufruf durch die Funktion selber ersetzen).

> Wenn ich nun aber "ordentlich" mein Programm auf mehrere Files (mit
> passenden Headerdateien) aufteilen will, geht "static" ja nicht, und der
> Flashverbrauch steigt.

Wenn du unbedingt inlinen willst, kommt die Funktion ausnahmsweise in 
die Header-Datei (natürlich mit dem Attribut static)

Allerdings: sobald die Funktion mehrmals aufgerufen wird, verkehrt sich 
dieser 'Vorteil' ins Gegenteil: Dann wird das Exe insgesammt größer, 
weil ja der Code für die Funktion mehrmals vorhanden ist: An jeder 
Stelle an der ein Funktionsaufruf erfolgte.

von Ernie (Gast)


Lesenswert?

Danke für die Antwort...

kann ich so aber nicht bestätigen.

Ich habe eine Beispielfunktion als static inline - wenn ich das static 
wegnehme, brauche ich 6 Byte mehr Flash (WinAvr 20070525)....

z.B. die aus Peter Danneggers Entprellroutine

static inline uint8_t get_key_press( uint8_t key_mask ) {

  key_mask &= key_press;              // read key(s)
  key_press ^= key_mask;              // clear key(s)

  return key_mask;
}

(ob das inline da ist oder nicht ist von der Flashgröße her bei mir 
egal)


Lg,

Berts Kumpel

von Karl H. (kbuchegg)


Lesenswert?

Ernie schrieb:

> Ich habe eine Beispielfunktion als static inline - wenn ich das static
> wegnehme, brauche ich 6 Byte mehr Flash (WinAvr 20070525)....

Logisch. Durch das static teilst du ja dem Compiler mit, dass diese 
Funktion nur in diesem *.c verwendet wird.
Die logische Konsequenz: Der Compiler kennt alle Stellen an denen die 
Funktion verwendet wird. Wird die Funktion überall inline eingesetzt, 
dann gibt es keinen Grund die Funktion selbst auch noch anzulegen.

Lässt du das static weg, dann ändert sich das Blatt. Die Funktion könnte 
ja auch noch von woanders her aufgerufen werden. Es reicht also nicht, 
einfach überall in diesem *.c den Funktionsaufruf durch den 
Funktionscode zu ersetzen. Es muss auch die Funktion selbst auch noch 
angelegt werden. Sie könnte ja von woanders her aufgerufen werden.

> (ob das inline da ist oder nicht ist von der Flashgröße her bei mir
> egal)

Weil die meisten Compiler inline sowieso ignorieren. Die entscheiden 
lieber selber, ob sich inlinen lohnt oder nicht und machen das auf 
eigene Faust, egal ob du das anforderst oder nicht. Das Schlüsselwort 
inline wird eigentlich nur noch dazu gebraucht, dem Compiler 
mitzuteilen, dass er Vorkehrungen treffen muss, dass diese Funktion 
unter Umständen mehrmals im Speicher existiert und er den Linker 
entsprechend insturieren muss.

'inline' ist wie 'register': Ein Relikt aus einer vergangenen Zeit, das 
von heutigen Compilern im Regelfall ignoriert wird.

von Ernie (Gast)


Lesenswert?

Ok,

also muß ich (wenn der Platz eng wird) doch die c-Files includen (und 
nicht "ordentlich" mit Header-Dateien arbeiten)?

Also nach dem Motto "Sauberer Programmierstil geht auf Kosten der 
Codegröße"?

Lg,

Berts Kumpel

von Karl H. (kbuchegg)


Lesenswert?

Ernie schrieb:
> Ok,
>
> also muß ich (wenn der Platz eng wird) doch die c-Files includen (und
> nicht "ordentlich" mit Header-Dateien arbeiten)?

Nein. C-Files includest du niemals.
Aber wie gesagt, du kannst deine Funktionen ausnahmsweise in diesem Fall 
in den Header ziehen. Spricht nichts dagegen. Aber dann als static 
inline markieren.

> Also nach dem Motto "Sauberer Programmierstil geht auf Kosten der
> Codegröße"?

Im Grunde: ja.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

-fwhole-program -combine könnte auch helfen, aber dann müssen
alle Quellmodule in der Kommandozeile des Compilers auf einmal
angegeben werden.

von Ernie (Gast)


Lesenswert?

Vielen Dank für die Antworten...

Da ich bei diesem Projekt mit jedem Byte ringe, kann ich also guten 
Gewissens ein wenig sauen - eine Ausrede habe ich ja nun ;-))

Lg,

Berts Kumpel

von Peter D. (peda)


Lesenswert?

Ernie schrieb:
> Ich habe eine Beispielfunktion als static inline - wenn ich das static
> wegnehme, brauche ich 6 Byte mehr Flash (WinAvr 20070525)....

Das liegt daran, daß der Compiler kleine Funktionen inlined, obwohl der 
Code größer wird.
Das static sagt ihm nur, daß er die eigentliche Funktion dann auch 
entfernen darf.

Mit dem Schalter
-fno-inline-small-functions
kannst Du ihm das abgewöhnen und dann wird Dein Code auch kleiner.



Peter

von Ernie (Gast)


Lesenswert?

Hallo Peter,

gibts das denn beim GCC 4.1.2 schon(hab ich nicht gefunden)?

Lg,

Berts Kumpel

von Sven P. (Gast)


Lesenswert?

VORSICHT

Das Inline-Schlüsselwort funktioniert in GCC noch garnicht so lange. Mal 
präventiv einen Blick auf die Version werfen!

von Ernie (Gast)


Lesenswert?

So, habs getestet,

-fno-inline-small-functions

kennt er noch nicht.

Lg,

Ernie

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sven P. schrieb:

> Das Inline-Schlüsselwort funktioniert in GCC noch garnicht so lange.

Es funktioniert schon so lange, wie ich GCC kenne.  OK, das ist noch
nicht so lange, knapp 20 Jahre vielleicht...

Was sich letztens geändert hat, ist das Verhalten von "extern
inline".  Sinnvollerweise sollte man aber (bis auf ganz wenige
Ausnahmen) "static inline" benutzen, wenn man schon inlining haben
will.

von Peter D. (peda)


Lesenswert?

Ernie schrieb:
> -fno-inline-small-functions
>
> kennt er noch nicht.

Dann kannst Du das Inlining speziell für eine Funktion verbieten:
1
#define NIL(x)   x __attribute__ ((noinline)); x
2
3
NIL( void my_function( void ))
4
{
5
... code
6
}
Du solltest aber besser nen aktuellen WINAVR (4.3.2) installieren.
4.1.2 ist schon viele Jahre her.


Peter

von Ernie (Gast)


Lesenswert?

Hallo Peter,

ich scheue mich vor dem Upgrade, da das Verhalten bezüglich der 
Codegröße sich wohl nicht so zum Guten entwickelt hat - oder habe ich 
was verpasst und der aktuelle GCC macht kleineren Code als der alte 
(4.1.2)?

Lg,

Ernie

von Sven P. (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> Was sich letztens geändert hat, ist das Verhalten von "extern
> inline".  Sinnvollerweise sollte man aber (bis auf ganz wenige
> Ausnahmen) "static inline" benutzen, wenn man schon inlining haben
> will.

Hmm, aus irgendeinem Grund aber hatte ich da mal nachgehakt; ich meine 
mich zu erinnern, dass das Verhalten von 'inline' und 'static inline' 
sich irgendwie verändert hatte...irgendwie sowas: Voher ging 'inline' 
nur durch den Linker, wenn explizit 'static' davorstand, nachher gings 
auch nur mit 'inline'.
Ist das das, was du meintest?

Ich schau aber nochmal nach.

von P. S. (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> Ernie schrieb:

>> also muß ich (wenn der Platz eng wird) doch die c-Files includen (und
>> nicht "ordentlich" mit Header-Dateien arbeiten)?
> Nein. C-Files includest du niemals.

Wieder so ein Dogma - klar kann er das machen, wenn er das moechte.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sven P. schrieb:

> Hmm, aus irgendeinem Grund aber hatte ich da mal nachgehakt; ich meine
> mich zu erinnern, dass das Verhalten von 'inline' und 'static inline'
> sich irgendwie verändert hatte...irgendwie sowas: Voher ging 'inline'
> nur durch den Linker, wenn explizit 'static' davorstand, nachher gings
> auch nur mit 'inline'.

"static inline" (also der wesentlich sinnvolle Fall) hat sich nicht
geändert.  Bei "extern inline" hatte der GCC jedoch eine andere
Variante traditionell implementiert, als sie nunmehr durch ISO C99
abgesegnet ist.  Das wurde kürzlich gedreht, der Default ist jetzt
C99-kompatibel, das vorherige GNU-Verhalten lässt sich durch die
Option -fgnu89-inline bzw. das Attribute gnu_inline erreichen.

Die Details müsste ich nachlesen, das dreht sich alles irgendwie
darum, ob und wie die nicht-inline Kopie der Funktion (die auf Grund
des "extern" ja nötig ist) noch angelegt wird, ggf. auch noch abhängig
davon, ob das Inlining überhaupt akzeptiert worden ist (der Compiler
kann das ja nach Gusto auch ignorieren, solange es nicht durch das
Attribute always_inline erzwungen wird).

von Ernie (Gast)


Lesenswert?

So, habe jetzt mal mit dem aktuellen GCC getestet - über 300 Byte mehr.

Nicht wirklich eine Option....

LG,

Ernie

von Karl H. (kbuchegg)


Lesenswert?

Peter Stegemann schrieb:
> Karl heinz Buchegger schrieb:
>> Ernie schrieb:
>
>>> also muß ich (wenn der Platz eng wird) doch die c-Files includen (und
>>> nicht "ordentlich" mit Header-Dateien arbeiten)?
>> Nein. C-Files includest du niemals.
>
> Wieder so ein Dogma - klar kann er das machen, wenn er das moechte.

LOL
OK. Ich korrigiere.
Solange du noch nicht weißt was du tust, und welche Fallen da auf dich 
warten, solltest du dich an die Daumenregel halten, dass C-Files nicht 
inkludiert werden, sondern getrennt jedes für sich compiliert wird. Das 
ist ein Vorgehen welches sich bewährt hat, per C-Sprachdesign gefördert 
und forciert wird und auch deine Toolchain ist darauf abgestimmt.

Ich finds allerdings nicht soooooo schlimm, wenn man jemand zum Anfang 
ein paar 'Dogmen' vorgibt, an die er sich ganz einfach halten soll. Das 
leidige Problem ist doch immer: Mit wievielen Informationen 
überschwemmst du jemanden, ehe er ganz einfach nicht mehr aufnahmefähig 
ist und die Erklärung, so gut sie auch gemeint ist, mehr Verwirrung 
schafft als Nutzen bringt.
Wenn er soweit ist, findet er schon raus, was es mit diesem Dogma auf 
sich hat und wann man es ignorieren kann, soll oder muss.

von Peter D. (peda)


Lesenswert?

Ernie schrieb:
> So, habe jetzt mal mit dem aktuellen GCC getestet - über 300 Byte mehr.

Dann probier mal mein NeverInLine Macro oder mache die Tastenroutinen in 
ein extra Objekt, dann kann er sie nicht mehr inlinen.


> Nicht wirklich eine Option....

Außer beim ATtiny2313 ist das aber nicht kritisch.
Die ATtiny85, 84, 861 haben ja bis 8kB und der ATmega328P bis 32kB.


-fwhole-program -combine
kann beim neuen WINAVR aber ne Menge rausholen.


Peter

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> Mit dem Schalter
> -fno-inline-small-functions
> kannst Du ihm das abgewöhnen und dann wird Dein Code auch kleiner.

Aber gerade bei kleinen Funktionen kann inlining durchaus Verbesserungen 
der Code Größe zur Folge haben.

Ich kenne das AVR ABI nicht, aber bei ARM ist das definitiv der Fall. 
Hier muss jeder Funktionsaufruf von einem Stackframe umgeben werden, was 
mindestens zwei Instruktionen benötigt. Dazu kommt der Rücksprung in der 
Funktion selbst. Falls ich eine Funktion habe, deren eigentlicher Code 
nur eine oder zwei Instruktionen enthält, dann habe ich immer einen 
Vorteil, unabhängig davon, von wievielen Stellen dieser Code aufgerufen 
wird.

Da laut gcc.info die Option -finline-small-functions genau das abwägt, 
sollte das Abschalten keinen Vorteil bringen.

Gruß
Marcus
http://www.doulos.com/arm/

von (prx) A. K. (prx)


Lesenswert?

Marcus Harnisch schrieb:

> Hier muss jeder Funktionsaufruf von einem Stackframe umgeben werden, was
> mindestens zwei Instruktionen benötigt.

Auch dann, wenn diese Funktion keine weitere Funktion aufruft, somit die 
Return-Adresse in R14 bleiben kann?

von (prx) A. K. (prx)


Lesenswert?

Marcus Harnisch schrieb:

> Da laut gcc.info die Option -finline-small-functions genau das abwägt,
> sollte das Abschalten keinen Vorteil bringen.

Tut sie aber. Nicht immer, aber teilweise deutlich spürbar. Die 
Kalkulation für's Inlining scheint nicht immer den realen Verhältnissen 
zu entsprechen.

von P. S. (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:

> Ich finds allerdings nicht soooooo schlimm, wenn man jemand zum Anfang
> ein paar 'Dogmen' vorgibt, an die er sich ganz einfach halten soll.

Besser waere: "Das solltest du nicht machen, solange du dich nicht 
auskennst."

> Wenn er soweit ist, findet er schon raus, was es mit diesem Dogma auf
> sich hat und wann man es ignorieren kann, soll oder muss.

Leider muss ich immer wieder feststellen, dass die Leute auch Jahre 
spaeter noch voller obskurer Regeln sind, deren Hintergrung sie 
ueberhaupt nicht verstanden haben und damit Leute belaestigen, die sehr 
wohl wissen, was sie tun.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

A. K. schrieb:
>> Hier muss jeder Funktionsaufruf von einem Stackframe umgeben werden, was
>> mindestens zwei Instruktionen benötigt.
>
> Auch dann, wenn diese Funktion keine weitere Funktion aufruft, somit die
> Return-Adresse in R14 bleiben kann?

Ja. In der ARM ABI gibt es den Teil des Stackframes, der von der 
aufrufenden Funktion erzeugt wird, und den der Funktion selbst. Ich 
bezog mich auf ersteren, der immer vorhanden ist, da die aufrufende 
Funktion keine Annahmen über die aufgerufene Funktion treffen kann. Du 
denkst an den Stackframe innerhalb der Funktion und der darf natürlich 
weggelassen werden, wenn nur bereits gesicherte Register verwendet 
werden, bzw garantiert ist, dass r14 (lr) erhalten bleibt.

In einigen Fällen können beide Teile dieses Stackframes in einer 
PUSH/POP Operation verschmelzen, wodurch der Größenvorteil des inlining 
verschwinden kann, wegen der zusätzliche Speicherzugriffe aber nicht der 
Geschwindigkeitsvorteil.

Gruß
Marcus
http://www.doulos.com/arm/

von (prx) A. K. (prx)


Lesenswert?

Marcus Harnisch schrieb:

> denkst an den Stackframe innerhalb der Funktion und der darf natürlich
> weggelassen werden, wenn nur bereits gesicherte Register verwendet
> werden, bzw garantiert ist, dass r14 (lr) erhalten bleibt.

Wir waren m.W. noch beim Thema inlining. Bei dem der Frame einen 
wesentlichen Anteil hat. Der Frame der aufrufenden Funktion ist 
gewöhnlich weniger kritisch.

von Peter D. (peda)


Lesenswert?

Marcus Harnisch schrieb:

> Da laut gcc.info die Option -finline-small-functions genau das abwägt,
> sollte das Abschalten keinen Vorteil bringen.

Zumindest beim AVR verschätzt er sich aber regelmäßig.

Der obige 2-Zeiler erzeugt 12 Byte Code, bei 2 * Inlining also 24 Byte.
Mit 1 * RET + 2 * CALL sinds aber nur 18 Byte, d.h. 6 Byte gespart.

Außerdem ist der Code unvollständig, es fehlen noch 2 Instruktionen, da 
der Zugriff atomar sein muß (Kommunikation mit Interrupt). Dann sinds 10 
gesparte Bytes bei nur 2 Aufrufen.


Peter

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

A. K. schrieb:
> Wir waren m.W. noch beim Thema inlining. Bei dem der Frame einen
> wesentlichen Anteil hat. Der Frame der aufrufenden Funktion ist
> gewöhnlich weniger kritisch.

Weniger kritisch -- als was? Jede PUSH/POP Operation besteht bei ARM aus 
genau einer Instruktion egal wieviele Register ich sichern muss. Daher 
ist das Retten der Register vor dem Funktionsaufruf in Hinblick auf die 
Code Größe genauso aufwendig wie das Retten der Register in der 
aufgerufenen Funktion.

Aber wir schweifen wirklich ab, da es ja ursprünglich um AVR ging. Was 
ich lediglich sagen wollte, war, dass das inlining kleiner Funktionen, 
auch bei mehrfachem Aufruf, durchaus zu kleinerem Code führen kann.

Gruß
Marcus
http://www.doulos.com/arm/

von Ernie (Gast)


Lesenswert?

Noch mal zum Ursprungsproblem -

es geht natürlich um einen Tiny2313, Flash steht bei 2046 (schon mit 
-min8 und -ffreestanding + _attribute_ ((noreturn))und die Variablen 
sind schon großteils in Register 2 - 15)..

Schön werde ich den Code wohl nicht mehr bekommen, aber es scheint alles 
perfekt zu laufen.

LG,

Ernie

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

-ffreestanding ist oft eine Pessimierung, keine Optimierung, denn
es schaltet bibliotheksbezogene Optimierungen aus.

von Ernie (Gast)


Lesenswert?

Bei mir hat es 2 Byte gebracht..... na ja - es würde also auch ohne 
gehen ;-)

LG,

Ernie

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Du solltest aber besser nen aktuellen WINAVR (4.3.2) installieren.
> 4.1.2 ist schon viele Jahre her.

Naja, wenn's wirklich um Codegröße geht, dann empfiehlt sich eher ein 
Downgrade auf 3.4.6 denn ein Updrage auf 4.was-auch-immer.

Bisher hab ich bei keiner 4.x Version vergleichbar guten Code erhalten 
wie für 3.4.6 (ATmega8/88/168). Selbst für den ATtiny24 war der Code 
kleiner, obwohl 3.4.6 im Gegebsatz zu 4.x noch nicht den vollen 
Instruktionssatz unterstüzt (MOVW z.B.)

Ausserdem hab ich in 3.4.6. noch keine Bugs gefunden und mir sind auch 
keine bekannt. Daß die Optimierungen in 4.x auf Boliden abzielen und für 
Hänflinge wie AVR oft kontraproduktiv sind, wurde hier schon öfter 
durchgekaut. Nur weil der 3.4.6 angestaubt ist, ist er nicht schlecht 
:-)

Johann

von Ernie (Gast)


Lesenswert?

den probier ich morgen doch glatt mal aus!

LG,

Ernie

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ernie schrieb:
> den probier ich morgen doch glatt mal aus!
>
> LG,
>
> Ernie

Bin mal gespannt, was da zu berichten ist...

Johann

von Peter D. (peda)


Lesenswert?

Ernie schrieb:
> es geht natürlich um einen Tiny2313, Flash steht bei 2046

Häng dochmal Dein Programm an, dann kann man mal die verschiedenen 
Optimierungen ausprobieren.


Peter

von Ernie (Gast)


Lesenswert?

Das ist noch nicht "reif" - deswegen frage ich ja auch nach den 
Möglichkeiten, den Code "hübsch" zu machen (damit ich nicht so viel 
Dresche kriege ;-))... letzte Tests für bestimmte Funktionen fehlen auch 
noch.
Mein Ziel ist es aber, es hier rein zu stellen, deine Entprellroutine 
war auch eine große Hilfe - vielen Dank!

LG,

Ernie

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ernie schrieb:
> Das ist noch nicht "reif" - deswegen frage ich ja auch nach den
> Möglichkeiten, den Code "hübsch" zu machen (damit ich nicht so viel
> Dresche kriege ;-))... letzte Tests für bestimmte Funktionen fehlen auch
> noch.

Register-Variablen
Diese sind nicht immer produktiv. Zusammen mit Libs kann dir alles um 
die Ohren fliegen, weil sie nicht mit -ffixed2 -ffixed3 ... generiert 
sind. Und ich weiß auch nicht, was passiert, wenn sich globale Regs mit 
der ABI querkommen. Das für globale Register.

Lokale Register sind mehr oder weniger sinnfrei, zumindest für Klasse 
"l". gcc 4.x ist ungeeignet, wenn globale Register asynchron verwendet 
werden: Er optimiert durchaus globale fixed Register in die Tonne.

Durch deine Register-Definitionen hast du praktisch keine call-saved 
Regsister mehr frei. Wenn du Pech hast, legt gcc Frames an (s.u.) und 
die "Optimierung" geht nach hinten los!

RAM-Zugriffe
Überfliege mal kurz dein List-File (aus dem .elf erstellt). Wenn du 
Nester von 4-Byte Zugriffen sieht, machst du was falsch. Zumindest 
bieten sich Möglichkeiten zu Optimierungen gegen Codegröße. (Implizites) 
Inlining kann da kontraproduktiv sein, weil gcc mehr Infos zu sehen 
bekommt, als gut ist.
Sammle zusammengehörige Daten in Strukturen. Das macht die Quelle klarer 
und ist kein Performance-Verlust. Es öffnet die Möglichkeit für 
indirekte Zugriffe (wenn man's richtig anstellt).

switch-case
Ist nur bedingt tauglich für kleinen Code. if-else-Orgie ist u.U. besser

Frame
Schau in deinem Code, ob Funktionen nen Frame anlegen. Wenn das 
vermieden werden kann, ist mächtig Code zu sparen. Nen Frame erkennst du 
daran, daß im Prolog SP verändert und Y als Frame-Pointer initialisiert 
wird.

Lokale Variablen
Operiere mit lokalen Variablen, nicht auf dem RAM. AVR hat recht viele 
Register, um Werte innerhalb einer Funktion zwischenzuspeichern. Aber 
nicht übertreiben, sonst wird man mit einem Frame bestraft.

CSE
1
if (a)
2
{
3
   u = 1
4
   v = 2;
5
}
6
else
7
{
8
   // wenn v und w unabhängig sind (etwa bei Speicherzugriff etc.)
9
   // ist es besser, zuerst w zu setzen und erst dann v. Dadurch
10
   // wird in manchen Fällen erst Common Subexpression Elimination
11
   // (CSE) möglich. Der Compiler kann das nicht immer analysieren,
12
   // du als Progger weißt aber um die Unabhängigkeit und daß die 
13
   // beiden Operationen kommutieren. Folgende Codierung ist also
14
   // nicht optimal:
15
   v = 4;
16
   w = 5;
17
}

Implizite Casts
Implizite Casts erweitern u.U auf int-Breite, was innerhalb von 
Berechnungen unnötig ist. Dürfte bei dir wegen -mint8 keine Rolle 
spielen, aber es sei trotzdem darauf hingewiesen.

Lesen ausm Flash
Geht's um kleinen, schnellen Code und hat man mehrer Bytes aus dem Flash 
zu lesen, dann hat man die wahl zwischen Pest und Cholera: pgm_read_byte 
et al. und memcpy_P. Siehe dazu auch die Diskussionen in
   Beitrag "[avr-gcc]. post-increment Version von avr/pgmspace.h"

Globale Flags
Neuere AVRs haben GPIORn-SFRs, die man frei verwenden kann. So auch 
ATtiny2313, bei dem alle drei bitadressierbar sind. Diese SFRs sind also 
daui prädestiniert, globale Flags zu enthalten. Sie lassen 
Setzen/Rücksetzen in einer Instruktion zu, bedingt Verzweigen in maximal 
2 Instruktionen.

Vermeide Bitfelder
Diese werden von avr-gcc schlecht unterstützt.

Geschickte Algorithmen
Nicht immer ist die libc/libgcc der Weissheit letzter Schluss :o)
   Beitrag "Re: registerinhalt in 2Byte aufteilen"

ISRs
Kleinste IRQ-Routinen in asm schreiben, das vermeidet Overhead durch 
avr-gcc (Sichern von _zero_reg_ etc.) In C nach Möglichkeit 
Funktionsaufrufe vermeiden.

Johann

von Ernie (Gast)


Lesenswert?

Danke für die Tips,

habe zwar nicht alles verstanden (mangelnde Kenntnis + komme gerade aus 
der Kneipe :-)(=) aber ich werde weiter nachforschen.
Die GPIO's sind leidlich ausgenutzt; Bitfelder- was ist das ? ;-), wenn 
ich die Register Variablen weglasse gibt wesentlich mehr auf die Mütze, 
-mint8 hat mehrere 100 Byte gebracht,  Beitrag "Registerinhalt in 2Byte 
aufteilen" - ich habe ein paar Assembler Geschichten von meinem Kumpel 
Frank zum Platz sparen drin - mehr muss (auch für das Verständnis) 
assemblermäßig auch nicht sein - sollte im Prinzip C bleiben.

Ich berichte morgen, was der "alte" Compiler gebracht hat - mein Traum 
wären so um die 130 Byte - dann stoße ich das ganze Konzept noch mal um.

LG,

Ernie

PS: Na Frank, liest du mit??

von Peter D. (peda)


Lesenswert?

Ernie schrieb:
> Das ist noch nicht "reif" - deswegen frage ich ja auch nach den
> Möglichkeiten, den Code "hübsch" zu machen (damit ich nicht so viel
> Dresche kriege ;-))...

Es geht mir nur rein um die Optimierungsmöglichkeiten (Compilerschalter, 
bestimmte Sequenzen).
Da hat sich bei den neueren GCC auch einiges verbessert (keine 
16bit-Erweiterung des Returnwertes, keine 16bit-Erweiterung bei 
switches, keine toten RETs).

Wie der Code aussieht, ist wurscht, Hauptsache er compiliert (0 Error, 0 
Warnings).


Peter

von Ernie (Gast)


Lesenswert?

Wenn das für dich wirklich interessant ist, schicke ich das natürlich 
erst mal persönlich  - wie oben schon erwähnt gibt es noch einen 2. 
(inzwischen ausgeschiedenen) Autor, den ich fair mit erwähnen will - 
natürlich muss dann alles rund sein.

Auf den neuen GCC gebe ich persönlich (was die AVR Strecke betrifft) 
keinen Pfifferling - der ist halt inzwischen für andere Kisten 
optimiert.

LG,

Ernie

PS: dann muß ich mich ja auch mal anmelden (bin immer zu faul...)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Es geht mir nur rein um die Optimierungsmöglichkeiten (Compilerschalter,
> bestimmte Sequenzen).
> Da hat sich bei den neueren GCC auch einiges verbessert (keine
> 16bit-Erweiterung des Returnwertes, keine 16bit-Erweiterung bei
> switches, keine toten RETs).

Ja, vor allem die return-Promotion nervt.

GCC 4.x ist teilweise schon krass, was Optimierung angeht. Eben hab ich 
wieder was gesehen: Ich wollte ne Dummie-Version von Division+Rest eines 
8-Bit Wertes. Um Quotent (quot) und Rest (rem) besser handhaben zu 
können, hab ich ne Struktur ähnlich div_t aus der libc definiert.

Die Berechnung auf Grundschul-Level sieht so aus:
1
#include <stdint.h>
2
3
typedef struct
4
{
5
  uint8_t quot;
6
  uint8_t rem;
7
} udiv8_t;
8
9
udiv8_t mydiv10_2 (uint8_t n)
10
{
11
    udiv8_t qrem = {.quot = 0, .rem = n};
12
    
13
    while (qrem.rem >= 10)
14
    {
15
        qrem.rem -= 10;
16
        qrem.quot++;
17
    }
18
        
19
    return qrem;
20
}

Es geht um Codegröße, Speed ist nicht von Belang (Ausgabe dauert eh 
ewig).

avr-gcc 3.4.6 bastelt daraus mit -Os für ATmega8
1
mydiv10_2:
2
  ldi r18,lo8(0)
3
  mov r19,r24
4
.L7:
5
  cpi r19,lo8(10)
6
  brlo .L6
7
  subi r19,lo8(-(-10))
8
  subi r18,lo8(-(1))
9
  rjmp .L7
10
.L6:
11
  movw r24,r18
12
  ret

Das sind 9 Words. Nun denkt man, gcc 4.x kann Register besser allokieren 
und spart das MOVW zum Ende. avr-gcc 4.3.2 erzeugt aus dem gleichen Code
1
mydiv10_2:
2
  mov r18,r24
3
  ldi r22,lo8(10)
4
  mov r24,r18
5
  rcall __udivmodqi4
6
  ret

Mal abgesehen von dem unnötigen MOV ist das absolut scharf: Er erkennt, 
daß die Dummie-Rechnung Division+Rest ist!!! Das ganze braucht nur 5 
Worte, allerdings fängt man sich aus der libgcc2 die __udivmodqi4 ein -- 
eine Aktion die man durch den Dummie-Algorithmus gerade vermeiden 
wollte. So genial die Optimierung ist, sie ging nach hinten los.

Nach einigem Rumspielen erweist sich -Os -fno-tree-loop-optimize als 
wirksam:

Der unliebsame Libcall verschwindet, und der Code belegt 8 Worte:
1
mydiv10_2:
2
  mov r25,r24
3
  ldi r24,lo8(0)
4
  rjmp .L2
5
.L3:
6
  subi r25,lo8(-(-10))
7
  subi r24,lo8(-(1))
8
.L2:
9
  cpi r25,lo8(10)
10
  brsh .L3
11
  ret
schwitz geschafft. Mit -O2 oder -O3 wird's dann aber total und 
vollends hirnverbrannt...

Ein Kumpel von mir ist Fahhrad-Freak. Sein Bike hat inzwischen ein 
Gewicht, wo ihm die Einsparung jedes weiteren Gramms mit 2€ zu Buche 
schlägt.

Mit Code-Optimierung ist es ähnlich: Anfangs sind Erfolge easy, aber je 
weiter man schon ist, desto aufwändiger wird es.

Johann

von Ernie (Gast)


Lesenswert?

Das Ergebnis mit GCC 3.4.6 war leider ernüchternd - 34 Byte mehr Flash.

LG,

Ernie

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ernie schrieb:
> Das Ergebnis mit GCC 3.4.6 war leider ernüchternd - 34 Byte mehr Flash.

Ohne den Code zu sehen kann man dazu nichts sagen. Es deutet aber stark 
darauf hin, daß dein Code offenbar ungünstig geschrieben ist.

Klar, ein Compiler sollte semantsch gleichwertigen Code immer optimal 
übersetzen, aber das ist bei real existierenden Compileren eben nicht 
immer der Fall. Die Quelle aufzupolieren kann also was bringen.

GCC 4.x ist klar besser bei "unsinnigem" Code. Wenn zum Beispiel in 
einem Modul ne globale Funktion steht, die nie referenziert wird, dann 
kann 3.4.6 die noch nicht rauswerfen. Und 4.x ist besser bei 
braindead-Code aus Codegeneratoren.

Den Nachteil der überflüssigen Returnwert-Promotion hat avr-gcc 3.4.6 
-mint8 zumindest nicht.

Johann

von Ernie (Gast)


Lesenswert?

<klar besser bei "unsinnigem" Code

nun aber mal ganz vorsichtig mit der Bewertung... ;-)

In dieser Größenordnung spielt das für mich eh keine Rolle, mit 30 Byte 
mehr oder weniger kann ich hier keine neue Funktionalität reinstecken - 
Wunder kann man eben nicht erwarten.

Ich merke mir das (GCC 3.4.6) aber als Variante, wenn ich wieder in 
große Platznot komme.

LG,

Ernie

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ernie schrieb:
> <klar besser bei "unsinnigem" Code
>
> nun aber mal ganz vorsichtig mit der Bewertung... ;-)

Das war nicht in Bezug auf deinen Code, den ich zudem nicht kenne. Aber 
wie gesagt macht 4.x so manches algebraische Kunststück und damit 
einiges wett, das zB auch Code aus nem Generator besser dastehen lässt.

Das obige -fno-tree-loop-optimize und -fno-move-loop-invariants (oder so 
ähnlich) machen den Code in 4.x evtl. auch kleiner. Aber Wunder gibt's 
damit auch nicht.

Johann

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
Noch kein Account? Hier anmelden.