Forum: Compiler & IDEs "high word" eines uint32_t "abspalten"


von Heiko L. (drcaveman)


Lesenswert?

Hallo!

Es geht hier um den AVR-GCC.

Ich müsste folgendes realisieren:
1
uint32_t a;
2
uint8_t b;
3
4
b = (a & 0x3FC00000) >> 22;

Der Compiler macht dann genau das, was man erwartet- eine Schleife mit 
22 Durchläufen und benutzt dabei alle vier Register, die für den 32Bit- 
Integer benutzt werden.

Rein logisch könnte man ja auch die oberen 16Bit von a "abknipsen" und 
nur um sechs stellen schieben, so dass auch im assembler nur die beiden 
oberen Register von a angesprochen werden.

Geht das oder kommt man da um den inline- assembler nicht herum?

Danke,
Heiko.

von Johannes M. (johnny-m)


Lesenswert?

Mach es doch einfach mit einer union oder mit Zeigern...
1
union{
2
    uint32_t dword;
3
    uint16_t word16[2];
4
}
Mit word16[0] und word16[1] kannste dann einfach auf die einzelnen 
16-Bit-Worte zugreifen.

von Heiko L. (drcaveman)


Lesenswert?

Hallo!

Das hier sieht brauchbar aus:
1
uint32_t a;
2
uint8_t b;
3
4
b = ((uint16_t)*(&a)) >> 6

Danke!

Edit: Oops, da fehlte wohl noch ein "*"...

von >>22 (Gast)


Lesenswert?

Bei mir gehen die 3 Varianten mit Zyklenzahl dabei
1
b = (unsigned char)((a/65536) & 0x3FC0) >> 6;  // 33 TZ
2
b = ((a&0x3FC0000)>>16) >> 6;   // 101 TZ
3
b = (a & 0x3FC00000) >> 22;    // 241 TZ

von Heiko L. (drcaveman)


Lesenswert?

Oh, da war doch noch mehr zu wurschteln:
1
uint32_t a;
2
uint8_t b;
3
4
b = (uint8_t) ( ((uint16_t) *( ((uint16_t *)(&a)) + 1 )) >> 6);

Das ist sehr lesbar ;)

Ich versuche dann auch mal die "union Version"

von Heiko L. (drcaveman)


Lesenswert?

>>22 wrote:
> Bei mir gehen die 3 Varianten mit Zyklenzahl dabei
>
>
1
> b = (unsigned char)((a/65536) & 0x3FC0) >> 6;  // 33 TZ
2
> b = ((a&0x3FC0000)>>16) >> 6;   // 101 TZ
3
> b = (a & 0x3FC00000) >> 22;    // 241 TZ
4
> 
5
>

Lustig, wie das auseinander driftet.

Kannst du auch mal die neue Pointer- Version ausprobieren?

von Heiko L. (drcaveman)


Lesenswert?

Heiko L. wrote:

> Ich versuche dann auch mal die "union Version"

So:
1
volatile uint32_t a;
2
3
union _u_a_split
4
{
5
    uint32_t* a32;
6
    uint16_t* a16;
7
};
8
9
void bla(void)
10
{
11
    uint8_t b;
12
    union _u_a_split a_split;
13
14
    a_split.a32 = &a;
15
    b = (uint8_t) (a_split.a16[1] >> 6);
16
}

Benötigt auf einem ATMega48 8 Taktzyklen mehr als die Pointer- Version 
(28 vs. 20). Sieht aber etwas schöner aus.

von Heiko L. (drcaveman)


Lesenswert?

Noch einmal eine kurze Zusammenfassung:

170 Zyklen:
1
b = (a & 0x3FC00000) >> 22;

166 Zyklen:
1
b = a >> 22;

28 Zyklen:
1
a_split.a32 = &a;
2
b = (uint8_t) (a_split.a16[1] >> 6);

20 Zyklen:
1
b = (uint8_t) ( ((uint16_t) *( ((uint16_t *)(&a)) + 1 )) >> 6);

von ... (Gast)


Lesenswert?

Die letzte Variante kann man aber auch kürzer schreiben:
1
b = *((uint16_t*)&a + 1) >> 6);
Beide Schreibweisen erzeugen bei mir den selben Code und brauchen nur 13 
Zyklen (Mega8, GCC 3.4.6 mit -O3)

CU

von ... (Gast)


Lesenswert?

PS: -Os erzeugt 8 Byte weniger Code, braucht dann aber 34 Zyklen.

von (prx) A. K. (prx)


Lesenswert?

10 Takte, gcc 4.3.3:
  b = ((unsigned)(a >> 16) << 2) >> 8;

Varianten über Speicher haben Nachteile. Der übrige Code, der auf die 
Variablen zugreift, wird aufwendiger weil nicht in Register. Und es 
erwingt oft einen teuren Stackframe.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. wrote:
> 10 Takte, gcc 4.3.3:
>   b = ((unsigned)(a >> 16) << 2) >> 8;
>
> Varianten über Speicher haben Nachteile. Der übrige Code, der auf die
> Variablen zugreift, wird aufwendiger weil nicht in Register. Und es
> erwingt oft einen teuren Stackframe.

Kommt drauf an, ob der Wert auto/parameter ist oder nicht.
1
unsigned long x;
2
unsigned char y;
3
4
void shift22_a (unsigned long x)
5
{
6
    y = ((unsigned)(x >> 16) << 2) >> 8;
7
}
8
9
void shift22_b ()
10
{
11
    y = (1[(unsigned*) &x] << 2) >> 8;
12
}

Ergibt in 4.3.2 mit -O2
1
shift22_a:
2
/* prologue: function */
3
/* frame size = 0 */
4
  movw r22,r24   ;  25  *lshrsi3_const/3  [length = 3]
5
  clr r24
6
  clr r25
7
  lsl r22   ;  24  *ashlhi3_const/4  [length = 4]
8
  rol r23
9
  lsl r22
10
  rol r23
11
  sts y,r23   ;  9  *movqi/3  [length = 2]
12
/* epilogue start */
13
  ret   ;  22  return  [length = 1]
14
15
shift22_b:
16
/* prologue: function */
17
/* frame size = 0 */
18
  lds r24,x+2   ;  6  *movhi/2  [length = 4]
19
  lds r25,(x+2)+1
20
  lsl r24   ;  24  *ashlhi3_const/4  [length = 4]
21
  rol r25
22
  lsl r24
23
  rol r25
24
  sts y,r25   ;  9  *movqi/3  [length = 2]
25
/* epilogue start */
26
  ret   ;  22  return  [length = 1]

Schneller dürfte es kaum gehen.

Johann

von Simon K. (simon) Benutzerseite


Lesenswert?

@Johann:
1
(unsigned*)

Hör mir auf mit Standard Int Datentypen. Dachte sowas gehört auf die 
Liste der verbotenen C-Features.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Simon K. wrote:
> Hör mir auf mit Standard Int Datentypen.

Es geht lediglich um einen Testfall. Ich kann gerne Testfälle posten mit 
den beliebten
1
#include "my-incredible-foo-defines.h"
2
#include "my-fancy-bar-types.h"
3
4
T f (T x)
5
{
6
   return F(x);
7
}

von let (Gast)


Lesenswert?

>170 Zyklen:
>
1
b = (a & 0x3FC00000) >> 22;

4 Zyklen:
1
shift2:
2
ldr  r3, .L3
3
mov  r0, r0, lsr #22
4
strb  r0, [r3, #0]
5
bx  lr

4 Zyklen:
1
void shift22_a (unsigned long x)
2
{
3
    y = ((unsigned)(x >> 16) << 2) >> 8;
4
}
5
6
shift22_a:
7
ldr  r3, .L7
8
mov  r0, r0, lsr #22
9
strb  r0, [r3, #0]
10
bx  lr
1
arm-none-eabi-gcc -O2 -S dummy.c

Sorry ;)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

let wrote:

> 4 Zyklen:
>
1
> shift2:
2
> ldr  r3, .L3
3
> mov  r0, r0, lsr #22
4
> strb  r0, [r3, #0]
5
> bx  lr
6
>
> arm-none-eabi-gcc -O2 -S dummy.c
> Sorry ;)

Und?

Heiko L. wrote:
> Es geht hier um den AVR-GCC.

Oder hast du per avr-gcc -B... nen anderen cc1 reingestöpselt? ;-)

Johann

von Heiko L. (drcaveman)


Lesenswert?

A. K. wrote:
> 10 Takte, gcc 4.3.3:
>   b = ((unsigned)(a >> 16) << 2) >> 8;
>
> Varianten über Speicher haben Nachteile. Der übrige Code, der auf die
> Variablen zugreift, wird aufwendiger weil nicht in Register. Und es
> erwingt oft einen teuren Stackframe.

Das braucht bei mir 19 Takte (a ist eine globale Variable im ram -> 4 
mal LDS...), also einen Takt weniger als die "Pointer- Version", aber 
immerhin!

Jetzt habe ich noch etwas probiert:
1
b = (*((uint16_t*)&a + 1) << 2) >> 8;

17 Takte!

Diese Version profitiert wohl einmal davon, dass hier wirklich nur das 
"high word" aus dem Speicher geholt wird (a ist global) und dann von dem 
einen "2er shift", der "8er shift" wird ja zu einer "Kopieraktion".

Danke!

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.