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
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.