Forum: Mikrocontroller und Digitale Elektronik GCC + Cortex: besser generell 32-Bit Variablen, obwohl 8/16-Bit reichen würden?


von Ralf (Gast)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von Gebhard R. (Firma: Raich Gerätebau & Entwicklung) (geb)


Lesenswert?

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

von Ralf (Gast)


Lesenswert?

@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

von (prx) A. K. (prx)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

> 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

von (prx) A. K. (prx)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

> 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

von (prx) A. K. (prx)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

@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

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.