Hallo zusammen!
Ich habe folgenden Codeausschnitt gesehen (in der Software eines 16Bit
Motorrollers):
1
unionChartoword
2
{
3
WORDw;
4
BYTEc[2];
5
};
Damit wird wohl ein WORD (16bit) in zwei BYTE (8bit) und umgekehrt
umgewandelt.
Jetzt stelle ich mir die Frage, ist es wohl sinnvoll solch ein union
auch auf einem 32Bit Arm Prozessor zu benutzen oder ergeben
Schiebeoperationen mehr Sinn? Der Arm verarbeitet doch standardmäßig
32Bit Daten. Ich kann mich an eine Passage in einem Buch (ich glaub das
hieß "ARM System Developers Guide") erinnern, in der steht "Use signed
and unsigned int types for local variables [...] This avoids casts and
uses the ARM's native 32Bit data processing instructions efficiently"
Ich denke an ein union in der Art:
Sven schrieb:> Jetzt stelle ich mir die Frage, ist es wohl sinnvoll solch ein union> auch auf einem 32Bit Arm Prozessor zu benutzen oder ergeben> Schiebeoperationen mehr Sinn?
Das musst du mit deinem Gewissen/Geschmack selber vereinbaren.
Zugriff auf die einzelnen Bytes eines größeren Datentyps per:
1) Schiebeoperationen
Portabel (funktioniert unabhängig von der Endianness), aber
möglicherweise ineffizient (je nach dem, wie gut der Optimizer des
Compilers ist)
2) Union
Immer effizient, aber nicht portabel (und eigentlich illegal).
>> Damit wird wohl ein WORD (16bit) in zwei BYTE (8bit) und umgekehrt> umgewandelt.
umgewandelt ist eigentlich das falsche Wort.
Gewandelt wird da nichts.
Mit der union werden die beiden Member 'übereinandergelegt'. D.h. es
gibt dann 2 Zugriffspfade auf denselben Speicherbereich. Dieselben 2
Bytes werden einmal in ihrer Gesamtheit als WORD aufgefasst und ein
anderes mal als 2 einzelne Bytes.
> Jetzt stelle ich mir die Frage, ist es wohl sinnvoll solch ein union> auch auf einem 32Bit Arm Prozessor zu benutzen oder ergeben> Schiebeoperationen mehr Sinn?
Die Sache ist die:
Die andere Möglichkeit wie man auf die Bytes eines WORD zugreifen kann,
besteht darin, sich einen Pointer umzucasten.
Es gibt 2 Problemkreise mit beiden Methoden (union + pointer casting)
* zum einen sind die beiden Operationen, wenn man es ganz streng nimmt
vom C Standard her 'undefiniertes Verhalten'.
Gut, das ist in der Praxis kein Thema, Auf allen bekannten Compilern
funktionieren beide Methoden so, wie man sich das vorstellt
* man liefert sich dem Byte-Sex aus, also der Reihenfolge was zuerst
im Speicher liegt: Das Low Byte oder das High Byte
wenn das kein Problem ist, dann ist es gut
wenn man allerdings die Zerlegung macht um damit zu einem komplett
anderen System zu transferieren, dann muss man das berücksichtigen.
Zudem muss man ganz einfach 'magisch wissen', wie der Byte Sex auf
seinem eigenen System ist.
All diese Nachteile hat die Methode mit Schieben nicht. Man kann
dezidiert und völlig systemunabhängig sagen, wie das Low-byte entsteht.
Das ergibt daher portablen Code.
> 32Bit Daten. Ich kann mich an eine Passage in einem Buch (ich glaub das> hieß "ARM System Developers Guide") erinnern, in der steht "Use signed> and unsigned int types for local variables [...] This avoids casts and> uses the ARM's native 32Bit data processing instructions efficiently"
Die Frage ist, inwiefern dieser Rat in der gegenständlichen Situation
überhaupt relevant ist. Eine derartige Union kommt normalerweise an
genau einer Stelle vor: Nämlich dann, wenn es gilt irgendwelche Daten
über irgendwelche I/O Kanäle auf den Weg zu bringen bzw. von dort zu
holen und ins restliche System einzuspeisen. An diesen Schnittstellen
ist die Sache aber überwiegend so, dass der Löwenanteil der Laufzeit
durch die eigentliche Behandlung der Schnittstellen draufgeht. Man sagt
die Applikation ist an dieser Stelle I/O-bound (das Gegenteil davon wäre
CPU-bound) und meint damit genau das: Die Laufzeit wird von der I/O
Hardware bestimmt. Eine Beschleunigung des Codes auf zb das 10-fache
beschleunigt die Datenübertragung nicht um das 10-fache.
>
Stefan Ernst schrieb:> 2) Union> Immer effizient
Quatsch. Wenn die entsprechende Architektur zum Extrahieren eines
Bytes aus dem 32-bit-Datenwort 8 Schiebeoperationen braucht, dann
braucht sie dafür 8 Schiebeoperationen, völlig unabhängig davon,
ob im C-Code ein ">> 8" steht oder eine union. Wenn die Hardware
einen Zugriff auf einzelne Bytes eines 32-bit-Wortes gestattet,
dann steht es dem Compiler natürlich frei, das Schieben und Maskieren
in einen direkten Zugriff des Bytes umzusetzen.
Spätestens bei einer 32-bit-Architektur sollte man sich vom Microsoft-
schen WORD als Bezeichnung einer 16-bit-Zahl wohl aber endgültig
verabschieden. Deren "word" ist, wenn überhaupt, eine 32-bit-Zahl,
die 16-bit-Zahl ist dann bestenfalls ein HWORD (half word).
Weithin sinnvoller ist es, statt dieser schwammigen Prosa gleich die
Bezeichnungen gemäß C99 <stdint.h> zu benutzen.
Jörg Wunsch schrieb:> Stefan Ernst schrieb:>> 2) Union>> Immer effizient>> Quatsch. Wenn die entsprechende Architektur zum Extrahieren eines> Bytes aus dem 32-bit-Datenwort 8 Schiebeoperationen braucht, dann> braucht sie dafür 8 Schiebeoperationen, völlig unabhängig davon,> ob im C-Code ein ">> 8" steht oder eine union.
Und was ist dann Quatsch? Ich schrieb "effizient" und nicht "effizienter
als überhaupt technisch möglich".
> Wenn die Hardware> einen Zugriff auf einzelne Bytes eines 32-bit-Wortes gestattet,> dann steht es dem Compiler natürlich frei, das Schieben und Maskieren> in einen direkten Zugriff des Bytes umzusetzen.
Ich sagte ja auch nicht, dass "Schieben und Maskieren" immer ineffizient
wäre, oder immer ineffizienter als die Union.
Ok, ich formuliere meine Aussage oben etwas um, dann wird vielleicht
deutlicher, was ich meine:
Die Möglichkeit/Gefahr, dass der Compiler Code erzeugt, der
ineffizienter ist, als eigentlich nötig, ist beim "Schieben und
Maskieren" größer als bei der Union.
Vielen Dank für die Antworten.
In der Tat geht es um I/O Geschichten. Es werden per CAN Bus Daten
empfangen, die dann zu 16 Bit daten zusammengesetzt werden und umgekehrt
werden 16bit ADC Daten per CAN verschickt...
Sprich also, im Prinzip ist es egal, ob ich caste und schiebe oder die
"Umwandlung" per union mache, da beiedes sowieso schneller ist als die
I/O Zugriffe.
Stefan Ernst schrieb:> 2) Union> Immer effizient, aber nicht portabel (und eigentlich illegal).
Unions führen i.d.R. zu Speicheroperationen. Auf Maschinen mit schnellem
Shifter (m.E. alle 32-Bitter) sind Register effizienter als Speicher.
> Die Möglichkeit/Gefahr, dass der Compiler Code erzeugt, der> ineffizienter ist, als eigentlich nötig, ist beim "Schieben und> Maskieren" größer als bei der Union.
Ist leider nicht so einfach. Bei High-Performance Architekturen wie den
meisten x86ern sind Lade/Schreiboperationen unterschiedlicher
Datenbreite auf die gleichen Daten u.U. ziemlich teuer (zweistellige
Takte).
Stefan Ernst schrieb:> Und was ist dann Quatsch?
Dass eine union immer effizient wäre. Wenn die Architektur keine
effiziente Auflösung kennt, kann die union es halt auch nur als
Schiebeoperation umsetzen.
Beide können durchaus identischen Code produzieren, Beispiel:
> Die Möglichkeit/Gefahr, dass der Compiler Code erzeugt, der> ineffizienter ist, als eigentlich nötig, ist beim "Schieben und> Maskieren" größer als bei der Union.
Dem würde ich unter A. K.s Vorbehalt zustimmen.
> Spätestens bei einer 32-bit-Architektur sollte man sich vom Microsoft-> schen WORD als Bezeichnung einer 16-bit-Zahl wohl aber endgültig> verabschieden. Deren "word" ist, wenn überhaupt, eine 32-bit-Zahl,> die 16-bit-Zahl ist dann bestenfalls ein HWORD (half word).
Stimmt nicht ganz. Soweit ich das weis, gilt folgendes:
BYTE----8 Bit
WORD---16 Bit
DWORD--32 Bit
QWORD--64 Bit
Jörg Wunsch schrieb:> Dass eine union immer effizient wäre. Wenn die Architektur keine> effiziente Auflösung kennt, kann die union es halt auch nur als> Schiebeoperation umsetzen.
Also macht sich unsere "Unstimmigkeit" in erster Linie an einer
unterschiedlichen Interpretation von "Effizienz von Code" fest. ;-)
Während du es eher absolut siehst (wenn die Architektur nicht effizient
ist, kann auch der Code nicht effizient sein), sehe ich es eher relativ
(Code ist effizient, wenn er die durch die Architektur vorgegebene
maximale Effizienz erreicht).
Jörg Wunsch schrieb:> Beide können durchaus identischen Code produzieren,
Das habe ich ja auch in keiner Weise bestritten. Im Gegenteil, ich würde
eigentlich bei jedem modernen Compiler erwarten, dass der Code gleich
ist. Auch im Hinblick auf A.K.s Einwand, denn der Compiler ist bei der
Union ja nicht dazu gezwungen, daraus einen unaligned Speicherzugriff zu
machen.
Nichtsdestotrotz war das "immer" natürlich eine Torheit. ;-)
Stefan Ernst schrieb:> Im Gegenteil, ich würde> eigentlich bei jedem modernen Compiler erwarten, dass der Code gleich> ist.
Na dann erwarete mal nicht zu viel von den "modernen" ;-)
Hinschreiben und staunen was rauskommt...
Sebastian Hepp schrieb:> Stimmt nicht ganz. Soweit ich das weis, gilt folgendes:> BYTE----8 Bit> WORD---16 Bit> DWORD--32 Bit> QWORD--64 Bit
Nur in Microsofts Denkweise. Für alle anderen ist "word" die natürliche
Wortgröße der CPU. Wenn du dir die Befehle einer RISC-CPU mal ansiehst,
wirst du dort entsprechende Modifier wie .h (für "half word") finden
können.
Stefan Ernst schrieb:> ist. Auch im Hinblick auf A.K.s Einwand, denn der Compiler ist bei der> Union ja nicht dazu gezwungen, daraus einen unaligned Speicherzugriff zu> machen.
Auf Alignment-Probleme habe ich mich dabei nicht bezogen. Die sind
vergleichsweise harmlos, maximal ein paar Takte mehr, und hierfür
ohnehin nicht relevant.
Absolut nicht harmlos ist es jedoch, ein Wort häppchenweise zu schreiben
und dann als Wort zu lesen. Damit fegst du bei einer out-of-order
Mikroarchitektur die halbe CPU leer, da der Load dann nicht aus der
spekulativen Store Queue bedient wird, sondern warten muss, bis alle
betreffenden Stores nicht mehr spekulativ und endgültig im Cache
angekommen sind. Das kann zig Takte dauern, da der spekulative Teil
heutiger CPUs mehr als hundert Befehle fassen kann.
A. K. schrieb:> Das kann zig Takte dauern, da der spekulative Teil> heutiger CPUs mehr als hundert Befehle fassen kann.
Und wie groß ist der spekulative Teil beim ARM?
Stefan Ernst schrieb:> Und wie groß ist der spekulative Teil beim ARM?
Inwieweit beispielsweise Cortex-Ax Cores von ähnlichen Problemen
betroffen sein können weiss ich nicht, die kenne ich nicht. Kleinere
Cores sind es jedenfalls nicht.
Mir ging es auch nur darum, der verallgemeinernden Aussage, der Weg über
Unions sei hinsichtlich der Effizienz sicherer, etwas entgegen zu
treten.
Wenn man sich auf Cores mit relativ kurzer nicht nennenswert
spekulativer Pipeline beschränkt, wie ARM7/9 oder Cortex-Mx, dann kann
man ziemlich risikofrei beide Wege gehen, der Unterschied wird so gross
nicht sein. Aufgrund der ARM Architektur habe ich aber den Verdacht,
dass die Shift-Variante etwas vorne liegen wird.
Wenn man jedoch über den Tellerrand hinaus guckt, dann wird es
schwierig, eine allgemeine Aussage zu treffen. Shifts können bei kleinen
8/16-Bit CPUs ohne Barrel-Shifter je nach Compiler katastrophal enden,
Unions bei den ganz grossen 32/64-Bittern.
Hab mal kurz in die Doku vom Cortex-A9 reingesehen. Es stehen zwar wenig
Details drin und nichts zum beschriebenen Problem, aber wird deutlich,
dass dieser Core einen erheblichen spekulativen Kontext besitzt. Ich
wäre folglich nicht überrascht, wenn dieser ARM-Core die Union-Technik
ebenfalls nicht allzu sehr mag.
Ich benutze immer nur die Schiebeversion. Damit bin ich von Compiler und
CPU unabhängig. Der Code läuft also überall.
Ich benutze vorzugsweise 8-Bitter und da ist das Schieben um Vielfaches
von 8Bit effizient programmiert, also kein merkbarer Overhead.
Peter
Peter Dannegger schrieb:> Ich benutze vorzugsweise 8-Bitter und da ist das Schieben um Vielfaches> von 8Bit effizient programmiert, also kein merkbarer Overhead.
Allerdings sollte man anfangs mal kontrollieren, ob der betreffende
Compiler die entsprechende Optimierung auch wirklich drin hat,
insbesondere bei 32-Bit Datentypen. Würde ich nicht bei jedem Compiler
blind drauf wetten.
@Sven:
Die Weisheit, dass man auf einer 32Bit CPU lokale Variablen ebenfalls
als 32Bit Typen deklariert kommt daher, dass man mit kleineren
Datentypen ohnehin keinen Platz spart (weder im Register noch auf dem
Stack). Die manchmal notwendige Vorzeichenerweiterung/Maskierung bei
kleineren Daten in großen Registern entfällt ebenfalls.
Deine Frage mit der Union bezieht sich aber eher auf die
Zugriffsbreite auf den Speicher.
Sven schrieb:> Es werden per CAN Bus Daten empfangen, die dann zu 16 Bit daten> zusammengesetzt werden und umgekehrt werden 16bit ADC Daten per CAN> verschickt...
Diese Information ist leider etwas dünn geraten. Poste doch mal
etwas mehr Code.
Ziel ist vermutlich, die Anzahl der Speicherzugriffe zu minimieren.
Falls die 16bit Daten mit geeigneter Endianess an geeigneter Stelle im
Speicher liegen, kannst Du Sie als 32Bit Daten lesen und mit den
einzelnen Halbworten... ja was eigentlich?
Oder liest Du die Daten als Bytes (z.B. aus einem Peripherie Register)
und willst sie als größere Bocken in einen Speicher schreiben?
Die ganze Diskussion um spekulative Zugriffe und bestimmte Cores lenkt
von der eigentlichen Thematik ab. Das kann wieder relevant werden,
falls wir den eigentlichen Code zu sehen bekommen. Wobei ich bezweifle,
dass Sven mit einem C-A9 arbeitet...
Gruß
Marcus
Es handelt sich um einen LPC2214 (ARM7tmdi)
Ich versuchs nochmal etwas präziser zu formulieren:
Ich habe in meinem Programm eine 32 Bit bzw. 16 Bit Variable und will
diese byteweise übertragen. Konkret geht es eben darum einen AD Wandler
wert (genau genommen den Mittelwert aus mehreren AD Werten) per CAN-Bus
zu übertragen. Dafür muss ich den Wert halt byteweise per SPI
Schnittstelle in einen externen CAN-Controller übertragen. Die zweite
Anwendung ist das auslesen einer Seriennummer aus einem EEPROM. Da lese
ich 4 Bytes aus und muss diese zur einer 32 Bit Seriennummer
zusammensetzen.
Hier mal die Funktion die eine 32 Bit Zahl byteweise aus dem EEPROM
liest:
Eine Union ist an dieser Stelle nicht sinnvoller, weil das Protokoll
dadurch abhängig von der Bytereihenfolge der beteiligen Maschinen wird.
Selbst wenn alle heute beteiligten Nodes die gleiche Bytereihenfolge
haben, ist sowas dennoch eine selbstgestellte Falle in die man Jahre
später mal reintappt.
A. K. schrieb:> haben, ist sowas dennoch eine selbstgestellte Falle in die man Jahre> später mal reintappt.
Und die Erfahrung zeigt, dass es nur eine Frage der Zeit ist, bis man
reintappt. Sicher ist hingegen, dass es irgendwann so weit ist.
Praktisch jeder 'clevere' Quick-Hack den man irgendwann einmal gemacht
hat, fällt einem irgendwann auf den Kopf.
Sven schrieb:> /*** HIER IST DAS WORUM ES GEHT ***/> /*** Die Frage ist, ist hier ein Union sinvoller ***/> *data = bytes[0];> *data += bytes[1] << 8;> *data += bytes[2] << 16;> *data += bytes[3] << 24;
Für diese Anwendung lohnt sich die Überlegung wegen der Effizienz
vermutlich nicht. Das geht dann schon eher in Richtung Geschmack.
Aber man spart durchaus ein paar Speicherzugriffe.
Du kannst die Definition der Union etwas umgestalten (kein array,
du nutzt es sowieso nicht als solches). Damit hast Du direkten
Einfluss auf die Reihenfolge der Bytes. Etwa so (ungetestet):
1
#if defined WORDS_BIGENDIAN /* autoconf */ || defined __BIG_ENDIAN /* armcc */
2
/* don't know what the GCC predefine is. Couldn't find any */
Sven schrieb:> Ich habe folgenden Codeausschnitt gesehen (in der Software eines 16Bit> Motorrollers):
So einen will Ich auch haben. Hast du eine Bezugsquelle?