Hi,
kann mir jemand sagen, ob's für Code-Performance/Speicherverbrauch eine
(große) Rolle spielt, wenn ich die Datentypen entsprechend des
"gebrauchten" Wertebereichs verwende? Oder sollte ich lieber generell
32-Bit-Variablen verwenden, da es sich beim Cortex ja um einen
32-Bit-Core handelt.
Generell macht man das ja so, dass man immer nur "passende" Datentypen
auswählt, um RAM-Speicherplatz zu sparen, und so hab ich das auch bei
meiner 8051-Software immer gemacht (da war's sowieso ein Muss :)
Der Cortex unterstützt ja byte- und wordweisen Zugriff, also wäre es
eigentlich auch hier angebracht, oder?
Wie kann ich solche Sachen selbst ermitteln & bewerten? Den
Codespeicherverbrauch der verschiedenen Implementierungen seh ich ja
nach einem Build. Den RAM-Verbrauch seh ich da auch (wobei das ja nicht
unbedingt was heissen mag).
Was die Geschwindigkeit angeht, bleibt eigentlich nur Assembler-Listing
generieren und -Befehlsreferenz studieren, oder? ;)
Ralf
Die Grösse statischer und globaler Daten geht eher gering in die
Laufzeit ein. Man sollte aber keinesfalls skalare lokale Variablen als
8- oder 16-Bit Typen deklarieren, sonst kann man sich deutlich Overhead
einhandeln.
Es gibt Leute, die für lokalen Daten konsequent den minimal nötigen Typ
wie uint16_t verwenden. Böser Fehler. Angebracht wäre hier "unsigned"
(oder für Puristen uint_fast16_t). Bei 8-Bit Typen ist sowas wie
uint_fast8_t sinnvoll, was auf die jeweils schnellste Datenbreite
rausläuft.
Beispiel:
uint16_t temp1 = a * b;
uint16_t temp2 = temp1 / 2 + c;
uint16_t temp3 = temp2 ...usw.
Hier muss u.U. jedes Zwischenergebnis im Register umständlich vor der
Weiterverarbeitung von 32-Bit auf 16-Bit runtergeschnippelt werden, was
bös auf die Performance gehen kann.
Ich denke, die Variablen sollte man so groß anlegen wie man sie braucht.
Bei Strukturen sollte man gut überlegen, um nicht unnötige "Löcher"
entstehen zu lassen.
>Was die Geschwindigkeit angeht, bleibt eigentlich nur Assembler-Listing>generieren und -Befehlsreferenz studieren, oder? ;)
zu aufwändig und fehleranfällig. Besser ist's einen Pin zu
setzen/rückzusetzen und mit dem Oszi die Zeiten zu messen.Das entspricht
dann genau der Wirklichkeit.
Grüsse
@prx:
> Die Grösse statischer und globaler Daten geht eher gering in die> Laufzeit ein. Man sollte aber keinesfalls skalare lokale Variablen als> 8- oder 16-Bit Typen deklarieren, sonst kann man sich deutlich Overhead> einhandeln.
statisch, global und lokal ist klar, aber was ist skalar?
Wenn ich das richtig verstehe sollte ich bei normalen Routinen immer
32-Bit-Typen verwenden?
> Es gibt Leute, die für lokalen Daten konsequent den minimal nötigen Typ> wie uint16_t verwenden. Böser Fehler. Angebracht wäre hier "unsigned"> (oder für Puristen uint_fast16_t). Bei 8-Bit Typen ist sowas wie> uint_fast8_t sinnvoll, was auf die jeweils schnellste Datenbreite> rausläuft.
Oi, jetzt hast du mich aber total erwischt :/
Da ist grad nur Bahnhof...
Ich dachte ein 'uint16_t' ist so oder so eine vorzeichenlose
16-Bit-Zahl, was bedeutet dann 'fast'?
Ich habe das entsprechende Headerfile gefunden, aber ich verstehe nicht
was da passiert. Es sieht auf den ersten Blick so aus, als ob vom
Compiler entsprechend der gewählten Architektur die Datentypen so
definiert werden, dass die effizientesten Instruktionen verwendet werden
können (auch wenn's etwas zu Lasten des Speichers geht).
> Beispiel:> uint16_t temp1 = a * b;> uint16_t temp2 = temp1 / 2 + c;> uint16_t temp3 = temp2 ...usw.>> Hier muss u.U. jedes Zwischenergebnis im Register umständlich vor der> Weiterverarbeitung von 32-Bit auf 16-Bit runtergeschnippelt werden, was> bös auf die Performance gehen kann.
Da hilft dann wohl Optimierungslevel hochschrauben auch nicht, oder? =)
Aber ernsthaft: das läuft dann eigentlich zusammen mit den o.g.skalaren
Variablen drauf raus, dass man im Code zwar beispielsweise eine
16-Bit-Variable angibt, aber nicht über uint16_t, sondern mit
uint_fast16_t, was für den Programmierer auf 16-Bit rausläuft, den
Compiler aber den performancetechisch besten Datentyp definieren lässt,
richtig?
@geb:
> Ich denke, die Variablen sollte man so groß anlegen wie man sie braucht.
Wie gesagt, ist eigentlich durch die 8051-Zeiten eh ins Hirn gebrannt :)
> Bei Strukturen sollte man gut überlegen, um nicht unnötige "Löcher"> entstehen zu lassen.
Mmmh, da meine ich im GCC Handbuch mal etwas vom packed Attribut
gelesen zu haben...
> zu aufwändig und fehleranfällig. Besser ist's einen Pin zu> setzen/rückzusetzen und mit dem Oszi die Zeiten zu messen.Das entspricht> dann genau der Wirklichkeit.
Ja, aber das hilft doch eigentlich nur bei einzelnen Programmteilen. Die
Auswirkungen auf die komplette Applikation kann man so ja auch nicht
ermitteln, oder? Ausser man macht es vielleicht in der main-Schleife und
erfasst die Anzahl der Signaländerungen über einen bestimmten Zeitraum.
Ralf
Ralf schrieb:> statisch, global und lokal ist klar, aber was ist skalar?
Das Gegenteil von Arrays und zusammengesetzten Typen wie Stukturen. Alle
Integers, Enums und Fliesskommatypen sind Skalare.
> Wenn ich das richtig verstehe sollte ich bei normalen Routinen immer> 32-Bit-Typen verwenden?
Ja, bei lokalen Variablen. Da ist der Platzbedarf ohnehin gleich, denn
ein Register ist ein Register, egal wieviele Bits sich nützlich machen.
Wenn die Bitbreite nicht bekannt ist oder es bis auf 8-Bitter runter
portabel sein soll: Type wie uint_fast8_t verwenden. Das sind die
effizientesten Typen mit mindestens 8 Bits, also bei 8-Bittern 8 Bits,
bei 32-Bittern oft 32.
Wenn 8 Bits nicht ausreichen, aber 16 Bits: int/unsigned tut es auch.
> Da hilft dann wohl Optimierungslevel hochschrauben auch nicht, oder? =)
Nur begrenzt, nämlich wenn der Compiler sieht, dass das Ergebnis nicht
grösser als 16 Bits sein kann (bei & beispielsweise). Wenn da aber
uint16_t temp1 = a * b;
steht, dann ist das Zwischenergebnis der Rechnung volle 32 Bits breit,
die Variable aber 16 Bits. Rechenoperationen und Register beim ARM
kennen nur 32 Bits, folglich muss man für eine unmittelbare
Weiterverarbeitung die überzähligen Bits aus dem Register aufwendig
rauswerfen.
> uint_fast16_t, was für den Programmierer auf 16-Bit rausläuft, den> Compiler aber den performancetechisch besten Datentyp definieren lässt,> richtig?
Ja.
> Mmmh, da meine ich im GCC Handbuch mal etwas vom packed Attribut> gelesen zu haben...
Führt zu ineffizientem Zugriff darauf. Die Strukturkomponenten der
Grösse nach sortieren ist effizienter.
> Das Gegenteil von Arrays und zusammengesetzten Typen wie Stukturen. Alle> Integers, Enums und Fliesskommatypen sind Skalare.stirnklopf Klar, skalar = nicht weiter (unter)teilbar. Jetzt fällts
mir wieder ein :)
> Ja, bei lokalen Variablen. Da ist der Platzbedarf ohnehin gleich, denn> ein Register ist ein Register, egal wieviele Bits sich nützlich machen.
Lokale Variablen werden immer in Registern verarbeitet? Okay, dann ist
das ein Unterschied zur 8051er-Welt. Dort wurden die Parameter in
Register gequetscht, die Variablen waren immer im RAM.
> Wenn die Bitbreite nicht bekannt ist oder es bis auf 8-Bitter runter> portabel sein soll: Type wie uint_fast8_t verwenden. Das sind die> effizientesten Typen mit mindestens 8 Bits, also bei 8-Bittern 8 Bits,> bei 32-Bittern oft 32.> Wenn 8 Bits nicht ausreichen, aber 16 Bits: int/unsigned tut es auch.
Ich würde dann die Variante mit 'fast' bevorzugen, allein schon wegen
dem psychologischen Effekt :) Das dürfte dann doch eigentlich aufs
gleiche rauslaufen wie die lokalen Variablen explizit immer als 32-Bit
(oder generell gesagt Core-Datenbreite) anzulegen. Oder hab ich's (immer
noch) falsch verstanden?
> Rechenoperationen und Register beim ARM kennen nur 32 Bits, folglich muss> man für eine unmittelbare Weiterverarbeitung die überzähligen Bits aus dem> Register aufwendig rauswerfen.
Okay, dann ist (noch) klar(er), warum die Verwendung von
32-Bit-Variablen (oder eben der 'fast'-Typen) sinnvoller ist.
> Führt zu ineffizientem Zugriff darauf. Die Strukturkomponenten der> Grösse nach sortieren ist effizienter.
Aber nur wenn die Reihenfolge im Speicher nicht wichtig ist (was
meistens der Fall ist), denke ich. Aber wenn eingelesene Datenströme
weiterverarbeitet werden sollen, dürfte es ganz praktikabel sein...
Ralf
Ralf schrieb:> Lokale Variablen werden immer in Registern verarbeitet?
Oft, je nachdem ob die Register ausreichen. Und selbst wenn sie nicht
ausreichen sollte man dem Compiler die Chance geben, phasenweise die
einen Vars effizient in Register zu legen und phasenweise andere. Gute
Compiler arbeiten so.
> Ich würde dann die Variante mit 'fast' bevorzugen, allein schon wegen> dem psychologischen Effekt :) Das dürfte dann doch eigentlich aufs> gleiche rauslaufen wie die lokalen Variablen explizit immer als 32-Bit> (oder generell gesagt Core-Datenbreite) anzulegen. Oder hab ich's (immer> noch) falsch verstanden?
Ich hatte die Portabilität erwähnt. Wenn du genau weisst, dass der Code
immer nur auf mindesten 16-Bittern laufen wird, dann kannst du auch
gleich int/unsigned verwenden. So war C ursprünglich ja gedacht. Sind
auch 8-Bitter auf der Rechnung sind die "fast" Typen der beste Ansatz.
Was keinen Sinn ergibt: uint32_t für Daten, die sowieso nicht mehr als 8
Bits benötigen, denn das signalisiert dem Leser das Gegenteil.
> Aber nur wenn die Reihenfolge im Speicher nicht wichtig ist (was> meistens der Fall ist), denke ich. Aber wenn eingelesene Datenströme> weiterverarbeitet werden sollen, dürfte es ganz praktikabel sein...
Ja. Exakt dafür gibts die gepackten structs.
Man sollte aber eben im Auge behalten, dass das Aus- und Einpacken je
nach Prozessor ziemlich aufwendig sein kann. Ich will grad nicht drauf
wetten, aber IIRC können zwar die CM3 misaligned accesses, nicht aber
die CM0/CM1.
Ralf schrieb:> stirnklopf Klar, skalar = nicht weiter (unter)teilbar. Jetzt fällts> mir wieder ein :)
Ich glaube du verwechelst das grad mit "atomar". Der Begriff "skalar"
entstammt der Mathematik und lässt sich nicht gut aus dem lateinischen
Ursprung ableiten.
> Ich hatte die Portabilität erwähnt. Wenn du genau weisst, dass der Code> immer nur auf mindesten 16-Bittern laufen wird, dann kannst du auch> gleich int/unsigned verwenden. So war C ursprünglich ja gedacht. Sind> auch 8-Bitter auf der Rechnung sind die "fast" Typen der beste Ansatz.
Ich glaube nicht, dass ich den Code den ich für die 32-Bitter mache auch
für die 8-Bitter verwenden werde.
> Was keinen Sinn ergibt: uint32_t für Daten, die sowieso nicht mehr als 8> Bits benötigen, denn das signalisiert dem Leser das Gegenteil.
Nein, reine Daten, also keine lokalen Variablen leg ich immer passend
an.
Ralf
Ralf schrieb:> Ich glaube nicht, dass ich den Code den ich für die 32-Bitter mache auch> für die 8-Bitter verwenden werde.
Hatte ich schon. Code für externe Bausteine wie LCD und DS18x20.
@Prx:
Sorry für die späte Antwort.
>> Ich glaube nicht, dass ich den Code den ich für die 32-Bitter mache auch>> für die 8-Bitter verwenden werde.> Hatte ich schon. Code für externe Bausteine wie LCD und DS18x20.
Ja, aber ich denke, ich werde die 8- und 32-Bitter-Projekte strikt
voneinander trennen. Sicher werde ich mir den einen oder anderen
Codeschnipsel kopieren, aber dann ist es eh getrennt.
Ralf