Forum: Mikrocontroller und Digitale Elektronik Shift vs. Union - Was ist effizienter?


von Karl Meinig (Gast)


Lesenswert?

Hallo mikrocontroller.net-Gemeinde,

ich bin neulich auf etwas gestoßen, worüber ich mich nun Frage, was 
besser/effizienter, programmiertechnisch Klüger, compilerfreundlicher 
und "schöner" ist.

Bisher kannte ich, folgendes um aus z.B. 4 uint8_t eine uint32_t mit 
Hilfe simpler shift-Operationen zu machen. Was ich in dieser Form, auch 
immer tatkräftig eingesetzt habe. Beispiel als #define (wobei das 
#define nicht weiter beachtet zuwerden braucht):
1
#define tr16(value) tr8((value) & 0xFF);tr8(((value) >> 8) & 0xFF)


Auf was ich dann vor einigen Tagen gestoßen bin ist, wäre folgendes 
Codebeispiel einer Funktion:
1
 
2
void convert(byte v)
3
{
4
union {
5
    uint32_t c;
6
    uint8_t b[4];
7
  };
8
  b[0] = v;
9
  b[1] = 0xff;
10
  b[2] = 0xff;
11
  b[3] = 0xff;
12
  return(c);
13
}


So, kann mir einer der Programmierprofi hier im Forum erklären, was 
besser/effizienter, programmiertechnisch Klüger, 
compilerfreundlicher/verständlicher und "schöner" ist?

Grüße
Karl

von MaWin (Gast)


Lesenswert?

Die union ist schneeler, WENN nicht der Compiler die shifts eliminiert.

Und void Funktionen können keinen uint32 zurückliefern, das nur am 
Rande, je nach Prozessor kann es auch uneffektiv sein uint32 als 
returnwert liefern zu müssen.

Ein 0xFFFFFF00UL|v wäre in dem speziellen Fall natürlich schlauer.

von Test (Gast)


Lesenswert?

Karl Meinig schrieb:
> besser/effizienter

Hängt vom Target ab. Probiere beides aus und wirf einen Blick in das ASM 
Listing.

von (prx) A. K. (prx)


Lesenswert?

MaWin schrieb:
> Die union ist schneeler, WENN nicht der Compiler die shifts eliminiert.

"Test" hat die richtige Antwort hinsichtlich Performance.
Shifts sind langsamer als Speicher (viele 8/16bit).
Shifts sind schneller als Speicher (viele 32bit).

Die union Variante ist nur geduldet, weil streng genommen unzulässig.

von Rudi (Gast)


Lesenswert?

Hi,

also die Union Variante ist etwas besser lesbar. Letztendlich hängt es 
aber von der Anwendung ab.
Aber warum sollte dieser Code unzulässig sein?

Gruß

von MaWin (Gast)


Lesenswert?

Rudi schrieb:
> Aber warum sollte dieser Code unzulässig sein?

Unzulässig nicht, die union gehört zum C Sprachschatz.

Aber es ist nicht definiert, auf welcher Stelle die Werte liegen, ob sie 
überlappen und welches bytes im long dann wo steht.

von Markus W. (Firma: guloshop.de) (m-w)


Lesenswert?

Rudi schrieb:
> also die Union Variante ist etwas besser lesbar. Letztendlich hängt es
> aber von der Anwendung ab.
> Aber warum sollte dieser Code unzulässig sein?

Unzulässig nicht, aber nicht generell portablel. Je nach 
Prozessorarchitektur kann das Ergebnis unterschiedlich sein. Kommt drauf 
an, in welcher Reihenfolge normalerweise Low- und High-Byte im Speicher 
landen.

Das Shifts bei 8-Bit-Prozessoren langsamer sind als das Umkopieren, ist 
meistens so, weil die kleinen Dinger in der Regel keinen Barrel-Shifter 
haben.

Trotzdem kommt es drauf an, was der Compiler draus macht, denn Shiften 
um 8 Positionen wird meistens als Umkopieren von Registerinhalten 
realisiert, und Shiften um 4 Positionen oft mit dem Swap-Nibble-Befehl, 
der bei AVR zum Beispiel auch nur einen Takt braucht.

von Marian (phiarc) Benutzerseite


Lesenswert?

MaWin schrieb:
> Rudi schrieb:
>> Aber warum sollte dieser Code unzulässig sein?
>
> Unzulässig nicht, die union gehört zum C Sprachschatz.
>
> Aber es ist nicht definiert, auf welcher Stelle die Werte liegen, ob sie
> überlappen und welches bytes im long dann wo steht.

Zusätzlich sagt ISO-C, dass ein Union ausschließlich gelesen wie 
geschrieben werden darf. Wenn ich union.a schreibe, darf ich nur und 
ausschließlich union.a lesen, nicht union.b, was dann undefined 
behaviour ist.

von Luther B. (luther-blissett)


Lesenswert?

Marian B. schrieb:

> Zusätzlich sagt ISO-C, dass ein Union ausschließlich gelesen wie
> geschrieben werden darf. Wenn ich union.a schreibe, darf ich nur und
> ausschließlich union.a lesen, nicht union.b, was dann undefined
> behaviour ist.

Was für ein ISO-C meinst du? C99 kann es nicht sein, dort steht auf 
Seite 73 unter Fussnote 83:

http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf
1
If the member used to access the contents of a union object is not the same as the member last used to
2
store a value in the object, the appropriate part of the object representation of the value is reinterpreted
3
as an object representation in the new type as described in 6.2.6 (a process sometimes called "type
4
punning"). This might be a trap representation.

Schon bevor diese Fussnote eingefügt wurde, wurde in Annex J1 unter 
"unspecified behavior" aufgelistet:

"The value of a union member other than the last one stored into"

"unspecified behavior" ist aber was völlig anderes als "undefined 
behavior".

von Marian (phiarc) Benutzerseite


Lesenswert?

In C89 war's unspecified behaviour, hast recht. Nicht undefined.

In MISRA-C gibt's dann noch ne Regel, die entsprechende Zugriffe 
verbietet.

: Bearbeitet durch User
von Luther B. (luther-blissett)


Lesenswert?

Marian B. schrieb:
> In MISRA-C gibt's dann noch ne Regel, die entsprechende Zugriffe
> verbietet.

MISRA-C 2004, Regel 18.4 sagt zuerst mal, das unions überhaupt für 
garnichts nicht genommen werden sollen. Und dann macht es Ausnahmen, 
falls es doch mal schnell gehen soll und man das Verhalten gut 
dokumentieren kann. Unter "Packing and unpacking data" steht dann dies 
als Beispiel für u.U. akzeptablen Code:
1
typedef union {
2
 uint32_t word;
3
 uint8_t bytes[4];
4
} word_msg_t;
5
uint32_t read_word_big_endian (void)
6
{
7
 word_msg_t tmp;
8
 tmp.bytes[0]=read_byte();
9
 tmp.bytes[1]=read_byte();
10
 tmp.bytes[2]=read_byte();
11
 tmp.bytes[3]=read_byte();
12
 return (tmp.word);
13
}

von Karl Meinig (Gast)


Lesenswert?

Also kann man sagen es ist "schlechter Stil" die union-Variante zu 
verwenden und es sollte der shift-Variante der Vorzug gegeben werden, da 
diese definiert macht was man erwartet.

Vielen dank euch allen noch, für die aufklärenden Worte.

von greg (Gast)


Lesenswert?

Was ist eigentlich mit der "Pointer-Casting-Variante"?
1
uint32_t word;
2
uint8_t *bytes = (uint8_t *)&word;
3
4
bytes[0] = x;
5
...
6
bytes[3] = w;

von PittyJ (Gast)


Lesenswert?

Portabel

Da wirst du mit der Union spätestens dann Probleme bekommen, wenn der 
Prozessor kein uint_8 kennt. Da ist z.B. bei der TI 28er DSP Reihe so.
Ein char oder unsigned char hat immer 16 Bit. Auch der Speicher ist 16 
bittig für jede Adresse.
Das Shiften würde auch auf dem TI Prozessor funktionieren, aber nicht 
die Union.

von (prx) A. K. (prx)


Lesenswert?

greg schrieb:
> Was ist eigentlich mit der "Pointer-Casting-Variante"?

Ist sauber, wenn bei dem Cast ein Pointer auf einen char Typ rauskommt. 
Andernfalls kann einem der Optimizer ein Schnippchen schlagen (gcc: 
siehe -fstrict-aliasing).

Er darf nämlich davon ausgehen, dass Pointer verschiedener Basistypen 
nicht auf das gleiche Objekt zeigen - ausgenommen bei char. Und dann 
kann es passieren, dass er, nach Änderungen an Daten, beim Zugriff über 
den falschen Pointer die vorherigen Daten liefert.

Sauber ist also:
  uint16_t var;
  uint8_t *p = (uint8_t *)&var;
  p[0] = ...;
  p[1] = ...;

Nicht sauber ist jedoch beispielsweise:
  uint8_t var[2];
  uint8_t *p = (uint16_t *)var;
  while (...) {
    var[0] = ...;
    var[1] = ...;
    ... = *p;
  }
Das wird besonders riskant, wenn solche Daten und Pointer durch 
Parameter wandern und damit anonymisiert werden.

Der zweite Fall ist zwar bei AVRs ziemlich verbreitet, bei denen 16-Bit 
I/O-Register wahlweise wort- und byteweise ansprechbar sind. Aber die 
sind volatile, da ist der Optimizer sowieso aussen vor.

: Bearbeitet durch User
von Marian (phiarc) Benutzerseite


Lesenswert?

A. K. schrieb:
> (gcc:
> siehe -fstrict-aliasing).
>
> Er darf nämlich davon ausgehen, dass Pointer verschiedener Basistypen
> nicht auf das gleiche Objekt zeigen - ausgenommen bei char. Und dann
> kann es passieren, dass er, nach Änderungen an Daten, beim Zugriff über
> den falschen Pointer die vorherigen Daten liefert.

strict-aliasing ist fundamental kaputt bei GCC. Auf keinen Fall 
benutzen. Zwar wird das was der GCC da macht vom Standard abgedeckt, 
aber das Verhalten ist dennoch kaputt, du lieferst ja auch schon ein 
Beispiel, wo man beim Debugging wie der Ochse vorm Berg sitzt und sich 
fragt, was der da für komischen Code erzeugt.

   long a;

   a = 5;
   *(short *)&a = 4;

Mit strict aliasing kann der GCC hier machen, was er will. a kann nach 
dem Block sowohl 5 als auch 4 sein.

von (prx) A. K. (prx)


Lesenswert?

Marian B. schrieb:
> strict-aliasing ist fundamental kaputt bei GCC. Auf keinen Fall
> benutzen. Zwar wird das was der GCC da macht vom Standard abgedeckt,

Nette Aussage - der GCC ist also fundamental kaputt, weil er sich am 
Standard orientiert?

Im Grund behauptest du hier, dass die C Standards fundamental kaputt 
sind, weil sie deinen Programmierstil nicht decken. ;-)

Dieser Aspekt der C Definition und der Implementierung in GCC hat schon 
seinen Sinn. Denn jegliches Aliasing berücksichtigen zu müssen ist ein 
böses Hindernis bei Codeoptimierung. Soll heissen, es macht Code 
deutlich langsamer. Und du willst doch wohl, dass dein Linux-PC, deine 
NAS-Box und dein Handy möglichst schnell sind, oder?

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

A. K. schrieb:

Copy&paste Fehler korrigiert:
Nicht sauber ist jedoch beispielsweise:
  uint8_t var[2];
  uint16_t *p = (uint16_t *)var;
  while (...) {
    var[0] = ...;
    var[1] = ...;
    ... = *p;
  }

von Marian (phiarc) Benutzerseite


Lesenswert?

A. K. schrieb:
> Im Grund behauptest du hier, dass die C Standards fundamental kaputt
> sind, weil sie deinen Programmierstil nicht decken. ;-)
>
> Dieser Aspekt der C Definition und der Implementierung in GCC hat schon
> seinen Sinn. Denn jegliches Aliasing berücksichtigen zu müssen ist ein
> böses Hindernis bei Codeoptimierung. Soll heissen, es macht Code
> deutlich langsamer. Und du willst doch wohl, dass dein Linux-PC, deine
> NAS-Box und dein Handy möglichst schnell sind, oder?

Du kennst die Debatte ja wahrscheinlich. Worst-Case Annahmen über 
Aliasing sind natürlich hinderlich bei der Optimierung. Aber nur weil 
der Standard (m.E. unsinngerweise) es erlaubt, braucht der Compiler 
nicht hingehen und Dinge, die sich eindeutig aliasen (wie z.B. a und 
*(short*)&a) als nicht-alias zu betrachten. M.E. ist der Sinn der 
entsprechenden Regelungen im Standard eher dem Compiler zu erlauben 
davon auszugehen, dass etwas kein Alias ist, wenn er ist nicht wirklich 
wissen kann. Hier kann er es aber wissen.

von (prx) A. K. (prx)


Lesenswert?

Marian B. schrieb:
> Mit strict aliasing kann der GCC hier machen, was er will. a kann nach
> dem Block sowohl 5 als auch 4 sein.

Oder 262149.

von (prx) A. K. (prx)


Lesenswert?

Marian B. schrieb:
> Du kennst die Debatte ja wahrscheinlich.

Nein, nicht wirklich.

> Aliasing sind natürlich hinderlich bei der Optimierung. Aber nur weil
> der Standard (m.E. unsinngerweise) es erlaubt, braucht der Compiler
> nicht hingehen und Dinge, die sich eindeutig aliasen (wie z.B. a und
> *(short*)&a) als nicht-alias zu betrachten.

Bei Programmiersprachen bin ich ein Freund formaler Spezifikationen. Und 
dieses "wissen kann" in der Sprachdefinition formal zu erfassen 
erscheint mir nicht als trivial.

Wenn du GCC dafür kritisierst, dass er mehr optimiert, als bei 
Mikrocontrollern gesund sein mag... Tja, dafür wurde und wird er nicht 
primär entwickelt.

Und wen das nervt, der hat eben -fno-strict-aliasing.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:

> Wenn du GCC dafür kritisierst, dass er mehr optimiert, als bei
> Mikrocontrollern gesund sein mag... Tja, dafür wurde und wird er nicht
> primär entwickelt.

Ich hab bisher noch nicht beobachtet, daß er Programme kaputtoptimiert.

Mit konstanter Regelmäßigkeit folt auf "das ist kaputt in GCC" oder 
"Optimierung muß für dieses Modul und GCC deaktiviert werden" falscher, 
d.h. nicht standardkonformer Code.

- Type Punning und und Strict Aliasing
- Falsche Verwendung von float / double
- Erzeugung misalignter Zeiger duch Casts
- Falsche Synchronisation (Atomarität, volatile, keine
  Synchronisationsprimitive, ...)

Hack wie bei *(short *)&a wird schlichweg nicht benötigt, denn memcpy 
tut das ganze Standarkonform und es wird vom GCC auch optimiert (es sei 
denn man nummt -ffreestanding oder -fno-builtin oder -O0).

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Ich hab bisher noch nicht beobachtet, daß er Programme kaputtoptimiert.

Doch, immer mal wieder hält er sich nicht an die wichtigste aller 
Vorgaben: Do what I mean, not what I say!

Beim Umgang mit Wertebereichsüberlauf und ähnlichen Spässen - eine 
Diskussion, die wir hier ja auch schon hatten - kommt auch immer wieder 
Mal Kritik auf. Erstens dass er sich nicht so verhält, wie der 
Programmierer intuitiv annimmt (ebd. DwImnwIs), zweitens weil er selbst 
dann nicht drauf hinweist, wenn er das (vermeintlich?) könnte.

> d.h. nicht standardkonformer Code.

Das Problem mit standardkonformem Code ist, dass man dafür den Standard 
kennen muss. Und es nicht ausreicht, mit der eigenen Intuition zu Werke 
zu gehen. Mancher updatet dann seine Intuition, andere kritisieren den 
Standard.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
>> Wenn du GCC dafür kritisierst, dass er mehr optimiert, als bei
>> Mikrocontrollern gesund sein mag... Tja, dafür wurde und wird er nicht
>> primär entwickelt.
>
> Ich hab bisher noch nicht beobachtet, daß er Programme kaputtoptimiert.

Er hat einige Optimierungen drin, die man bei einem Compiler, der auf 
die Kategorie AVR, MSP430 und LPC800 abzielt, nicht unbedingt 
implementieren würde. So führt Optimierung machmal auch dazu, dass eine 
teure Division, die man selbst ausdrücklich vor einer critical Section 
platzierte, vom Optimizer genau da hinein verschoben wird. Und man 
ggf. in den Code schauen muss und irgendwelche Hacks braucht, um das zu 
verhindern.

: Bearbeitet durch User
von greg (Gast)


Angehängte Dateien:

Lesenswert?

So, falls Interesse besteht, habe mal mit angehängtem Code und 
verschiedenen Compilern experimentiert:

1. ARM, gcc 4.8.3, -Ofast -mthumb -mcpu=cortex-m0plus

Generiert sehr ähnlichen Code für alle drei Fälle, immer Shifts und 
bitwise ORs. Wenn man -Os als Optimierung verwendet, generiert der gcc 
erstaunlicherweise für die Casting-Variante deutlich schlechteren 
(längeren) Code!

2. x86_64, gcc 4.7.3, -Ofast

Unions generieren sehr umständlichen Code, Shifts sind besser, Casting 
ist sehr kurz und prägnant.

3. 8051, sdcc 3.1.0

Hier mal ein Beispiel von einem schlechter optimierenden Compiler, der 
mehr Händchenhalten braucht. Der Code für die Shift-Variante ist sehr 
lang, da er praktisch ohne Tricks das umsetzt, was im Sourcecode steht. 
Der 8051er hat keinen Barrel Shifter, und so wird hier eine ganze Menge 
Code nur für das Shiften erzeugt. Ca. 50 Instructions!

Die Union- und Casting-Variante generieren aber guten, identischen Code.

von (prx) A. K. (prx)


Lesenswert?

greg schrieb:
> Der 8051er hat keinen Barrel Shifter, und so wird hier eine ganze Menge
> Code nur für das Shiften erzeugt. Ca. 50 Instructions!

Mit Shifts oder mit Moves? GCC/AVR setzt die Shift-Variante völlig ohne 
Shifts um, aber da er das mit den Shifts natürlich einzeln macht ist es 
trotzdem ziemlich umständlich.

Allerdings hast du da einen kleinen Fehler drin, denn
  (y << 8)
wird als "int" ausgewertet und dann auf 32 Bits vorzeichenerweitert. 
Folglich kopiert sich bei 8/16-Bittern ein gesetztes Bit 7 von "y" in 
die Bits 16..31 vom Ergebnis.

Besonders elegant ist der Code bei ARM-Original und Thumb2. Die 
Cast-Variante reagiert recht sensibel auf die exakte Einstellung des 
Optimizers.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

PS: Wenn man sowas "in gross" macht, also beispielsweise auf aktuellen 
x86 oder Cortex-A, ist jede Variante, die Bytes wirklich in Speicher 
schreibt und sofort in voller Breite ausliest übrigens unabhängig von 
der Anzahl Befehle eine nackte Katastrophe. Weil deren Cores das nur 
sehr ineffizient umsetzen und man eine zweistellige Anzahl Stall-Zyklen 
riskiert.

: Bearbeitet durch User
von greg (Gast)


Lesenswert?

A. K. schrieb:
> Mit Shifts oder mit Moves? GCC/AVR setzt die Shift-Variante völlig ohne
> Shifts um, aber da er das mit den Shifts natürlich einzeln macht ist es
> trotzdem ziemlich umständlich.

sdcc hat auch diese Optimierung. Er macht aus den Shifts Moves, da es ja 
byteweise Shifts sind. Aber für jeden Shift einzeln für alle 32 bit, und 
dann verodert er die ganzen Resultate.

Der gcc scheint das beim AVR so ähnlich zu machen, aber mit weniger 
Overhead. Richtig gut ist da die Union-Variante: nur 4 moves, sonst nix.

> Allerdings hast du da einen kleinen Fehler drin, denn
>   (y << 8)
> wird als "int" ausgewertet und dann auf 32 Bits vorzeichenerweitert.
> Folglich kopiert sich bei 8/16-Bittern ein gesetztes Bit 7 von "y" in
> die Bits 16..31 vom Ergebnis.

Ja, das hab ich übersehen. Die anderen Shifts haben ja den richtigen 
Cast. Aber es scheint nicht grundsätzlich etwas am Ergebnis zu ändern.

von greg (Gast)


Lesenswert?

greg schrieb:
> Aber es scheint nicht grundsätzlich etwas am Ergebnis zu ändern.

Damit meine ich natürlich den generierten Code, nicht das Resultat der 
Rechnung.

von (prx) A. K. (prx)


Lesenswert?

greg schrieb:
> Aber es scheint nicht grundsätzlich etwas am Ergebnis zu ändern.

Oh doch. Die falsche Variante ist bei AVR viel kürzer, 16 vs 26 Befehle 
im Kern.

: Bearbeitet durch User
von greg (Gast)


Lesenswert?

A. K. schrieb:
> Oh doch. Die falsche Variante ist bei AVR viel kürzer.

Das kann ich hier nicht nachvollziehen. Der generierte Code ist etwas 
anders, klar, aber er hat die gleiche Länge.

von (prx) A. K. (prx)


Angehängte Dateien:

Lesenswert?

avr-gcc 4.7.2, -O2

von greg (Gast)


Lesenswert?

A. K. schrieb:
> avr-gcc 4.7.2, -O2

Ist da nicht der Cast an der falschen Stelle? Du castest doch, nachdem 
die Vorzeichenerweiterung durchgeführt wurde. Richtig müsste der Cast 
wie bei den anderen stehen, z.B.:
1
((uint32_t)y << 8)

von (prx) A. K. (prx)


Lesenswert?

greg schrieb:
> Ist da nicht der Cast an der falschen Stelle?

Nein, das passt so. Das Ergebnis von
  (unsigned)(y << 8)
wird ohne Vorzeichen erweitert.

Genauso möglich und sinnvoll wäre
  ((unsigned)y << 8)

Hingegen ist dein
  ((uint32_t)y << 8)
zumindest vom Prinzip her umständlicher als nötig.

NB: Da es hier um die implizite Rechnung als "int" geht, bzw. um die 
Vermeidung einer Vorzeichenerweiterung dadurch, ist hier "unsigned" 
sinnvoller als uint16_t, weil bei 32-Bittern neutral.

: Bearbeitet durch User
von greg (Gast)


Lesenswert?

A. K. schrieb:
> Nein, das passt so. Das Ergebnis von
>   (unsigned)(y << 8)
> wird ohne Vorzeichen erweitert.

Nein, das haut nicht hin. Prüfe es selbst einmal nach.

A. K. schrieb:
> Beachte auch, dass die Verwendung von "unsigned" hier sinnvoller ist als
> uint16_t, da letzteres bei 32-Bittern den Code deutlich verschlechtern
> kann.

Daher (und der Einheitlichkeit halber) habe ich ja auch uint32_t 
verwendet. Was soll daran so umständlich sein? sdcc und avr-gcc haben 
deswegen keinen fetteren Code produziert.

von greg (Gast)


Lesenswert?

greg schrieb:
> Nein, das haut nicht hin. Prüfe es selbst einmal nach.

Okay, muss korrigieren, das haut durchaus hin. Aber führt beim avr-gcc 
eben zu enormem Code-Bloat; meine Variante nicht.

Wenn int 16 Bit breit ist, und man einen char 8 Bit nach links shiftet, 
dann shiftet man ins Sign-Bit rein. Wahrscheinlich will der gcc das 
irgendwie gesondert behandeln.

TL;DR: Die Shift-Variante ist relativ tricky durch die ganzen 
Typkonversionen. ;)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Hier noch das Beispiel mit memcpy:
1
#include <stdint.h>
2
#include <string.h>
3
4
uint32_t to_dw_memcpy (uint8_t x, uint8_t y, uint8_t z, uint8_t w)
5
{
6
    uint32_t r;
7
    uint8_t a[4] = { x, y, z, w };
8
    memcpy (&r, a, 4);
9
    return r;
10
}

Ergebnis mit avr-gcc 4.6, 4.7 oder 4.8:
1
to_dw_memcpy:
2
  mov r23,r22
3
  mov r25,r18
4
  mov r22,r24
5
  mov r24,r20
6
  ret

von (prx) A. K. (prx)


Lesenswert?

greg schrieb:
>> Beachte auch, dass die Verwendung von "unsigned" hier sinnvoller ist als
>> uint16_t, da letzteres bei 32-Bittern den Code deutlich verschlechtern
>> kann.

> Daher (und der Einheitlichkeit halber) habe ich ja auch uint32_t
> verwendet. Was soll daran so umständlich sein? sdcc und avr-gcc haben
> deswegen keinen fetteren Code produziert.

Dass es real nicht umständlicher wurde liegt an der Optimierung des 
Compilers. Weil der die Shifts durch Moves ersetzt. Jedoch ist
   (uint32_t)y << n
auf 8/16-Bit CPUs komplexer als
   (uint32_t)((unsigned)y << n)
es sei denn n ist konstant und anders als durch Shifts implementierbar.

In meinem obigen Absatz wollte ich darauf hinaus, dass
   (uint32_t)((uint16_t)y << n)
bei 32-Bittern umständlicher als
   (uint32_t)((unsigned)y << n)
werden kann, wenn der von Anfang an in 32 Bits rechnet und das Resultat 
auf 16 Bits abschneidet. GCC erkennt allerdings mitunter, dass es aus 
den Typen und Operationen abgeleitet nicht nötig ist. Verwendet man hier 
unsigned statt uint16_t, dann stimmt es bei 8/16-Bittern und bei 
32-Bittern ist der Cast faktisch funktionslos.

: Bearbeitet durch User
von wat (Gast)


Lesenswert?

A. K. schrieb:
> ist hier "unsigned" sinnvoller als uint16_t, weil bei 32-Bittern neutral.

wat.

unsigned ist nur Kurzschreibweise für unsigned int.

von (prx) A. K. (prx)


Lesenswert?

wat schrieb:
> unsigned ist nur Kurzschreibweise für unsigned int.

Danke für den wichtigen Hinweis ;-). Aber was willst du damit sagen?

Denn "unsigned [int]" ist nicht notwendigerweise identisch mit uint16_t, 
hat aber die gleiche Grösse wie "int", und genau um geht es hier.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Ergebnis mit avr-gcc 4.6, 4.7 oder 4.8:

GCC/ARM 4.7.2 von CodeSourcery kennt den Trick nicht und geht über den 
Speicher. Und genau das kann bei den Cortex-A die Suppe versalzen.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> Johann L. schrieb:
>> Ergebnis mit avr-gcc 4.6, 4.7 oder 4.8:
>
> GCC/ARM 4.7.2 von CodeSourcery kennt den Trick nicht und geht über den
> Speicher. Und genau das kann bei den Cortex-A die Suppe versalzen.

Das ist aber seht seltsam, denn da ist überhaupt nix AVR-spezifisches 
dabei (im Gegensatz zum Ziehen einer teuren Division über ein SEI / CLI, 
was eine Eigenart von avr-gcc ist).  Evtl hat es mit alignment zu tun 
und man brauch __builtin_assume_aligned oder sowas?

von greg (Gast)


Lesenswert?

Naja, aus verschiedenen Gründen ist oft -fno-builtins aktiv... und dann 
macht der gcc natürlich kein optimiertes memcpy, sondern ruft stupide 
die Library-Funktion auf.

von (prx) A. K. (prx)


Lesenswert?

greg schrieb:
> macht der gcc natürlich kein optimiertes memcpy, sondern ruft stupide
> die Library-Funktion auf.

Nope, das war es auch nicht.

4.7.2:
  strb  r0, [sp, #4]
  strb  r1, [sp, #5]
  strb  r2, [sp, #6]
  strb  r3, [sp, #7]
  ldr  r0, [sp, #4]

4.8.1:
  orr  r0, r0, r1, asl #8
  orr  r0, r0, r2, asl #16
  orr  r0, r0, r3, asl #24

: Bearbeitet durch User
von greg (Gast)


Lesenswert?

Liegt das nur an der Standardkonfiguration des CodeSourcey-GCCs oder ist 
das tatsächlich eine Macke, die immer auftritt? Builtins sind ein 
ziemlich altes GCC-Feature, und wenn memcpy als Builtin aktiv ist, dann 
sollte das auch benutzt werden.

von greg (Gast)


Lesenswert?

A. K. schrieb:
> Nope, das war es auch nicht.

Hm, okay. Ich dachte du verstehst mit "über den Speicher" den Verzicht 
auf Builtins.

von (prx) A. K. (prx)


Lesenswert?

Blöd ist, das er das nun zwar sauber optimiert, aber nicht merkt, dass 
der Speicher nicht mehr gebraucht wird. Weshalb die Funktion insgesamt 
so aussieht:
  orr  r0, r0, r1, asl #8
  orr  r0, r0, r2, asl #16
  orr  r0, r0, r3, asl #24
  sub  sp, sp, #8
  add  sp, sp, #8
  bx  lr

: Bearbeitet durch User
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.