Forum: Mikrocontroller und Digitale Elektronik PAritätszähler unglaublich langsam auf ATSAMD


von Harstad (Gast)


Lesenswert?

Hi,

ich habe hier eine simple Bitzählroutine, die mir ermittelt, wie viele 
1er Bits in einem Wert vorkommen und diese dann mit einem 
Paritäts-Zähler vergleicht:
1
static __attribute__((always_inline)) char parityOK(const uint_fast32_t vx,const uint_fast32_t vy)
2
{
3
   uint_fast32_t bitmask=0x00200000;
4
   uint_fast8_t  cx=0,cy=0;
5
   
6
   while (bitmask>0x00000002)
7
   {
8
      if ((vx & bitmask)==bitmask) cx++;
9
      if ((vy & bitmask)==bitmask) cy++;
10
      bitmask=bitmask>>1;
11
   }   
12
   
13
   if ((vx & 0x00000003)!=(cx & 0x00000003))
14
   {
15
      return 0;
16
   }
17
   if ((vy & 0x00000003)!=(cy & 0x00000003))
18
   {
19
      return 0;
20
   }
21
   return 1;
22
}

vx und vy sind dabei 22-Bit-Zahlenwerte. In den oberen 20 Bits wird 
gezählt, wie viele Einsen darin vorkommen und das Ergebnis wird mit dem 
in den unteren zwei Bits stehenden Zahlenwert verglichen (da natürlich 
nur die unteren 2 Bit/max. 3 Werte, alles was darüber hinausgeht, wird 
ignoriert, da nicht angenommen wird, dass mehr als 3 Bits gleichzeitig 
kippen).

Mein Problem: dafür, dass diese Funktion so simpel ist, ist sie auf 
einem mit 120 MHz getakteten ATSAMD unglaublich langsam und haut mir 
mein ganzes Timing durcheinander.

Meine Frage jetzt: wie kann man das optimieren? Gibt es vielleicht eine 
Funktion, mit der man die CPU die Bits zählen lassen kann, so dass ich 
mir diese Schleife sparen kann?

Danke!

von ooops (Gast)


Lesenswert?

Harstad schrieb:
> ist sie auf
> einem mit 120 MHz getakteten ATSAMD unglaublich langsam

Wie langsam ist denn "unglaublich"?

Hab gerade meine Formelsammlung mit den Konstantendefinitionen
nicht greifbar.

von Harstad (Gast)


Lesenswert?

ooops schrieb:
> Harstad schrieb:
>> ist sie auf
>> einem mit 120 MHz getakteten ATSAMD unglaublich langsam
>
> Wie langsam ist denn "unglaublich"?
>
> Hab gerade meine Formelsammlung mit den Konstantendefinitionen
> nicht greifbar.

So langsam, dass es für meine Belange zu langsam ist. Für die 
Fragestellung ist diese Information übrigens komplett unwichtig, 
schließlich möchte ich wissen, wie man das optimieren kann und nicht, ob 
der Prozessor richtig arbeitet.

von blub (Gast)


Lesenswert?

Schau mal nach ,,Brian Kernighan Algorithm" zum Bit-Zählen!

ich zitiere:

>We calculate n-1
>We and it with n i.e n&(n-1)
>Thus unset the rightmost bit
>Keep repeating the above steps until we end up with 0

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?


von hmmm (Gast)


Lesenswert?

Ich schmeiß mal __builtin_popcount in die Runde - keine Ahnung, was das 
beim ATSAMD ergibt...

von hmmm (Gast)


Lesenswert?

Ich bin einfach mal vom gcc ausgegangen...
__builtin_parity gäb's auch noch.
 --> https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

hmmm schrieb:
> Ich schmeiß mal __builtin_popcount in die Runde - keine Ahnung, was das
> beim ATSAMD ergibt...

Ergibt mit -mcpu=cortex-m0 -mthumb das hier:
1
00008128 <__popcountsi2>:
2
    8128:       4a09            ldr     r2, [pc, #36]   ; (8150 <__popcountsi2+0x28>)
3
    812a:       0843            lsrs    r3, r0, #1
4
    812c:       4013            ands    r3, r2
5
    812e:       1ac0            subs    r0, r0, r3
6
    8130:       0003            movs    r3, r0
7
    8132:       4a08            ldr     r2, [pc, #32]   ; (8154 <__popcountsi2+0x2c>)
8
    8134:       0880            lsrs    r0, r0, #2
9
    8136:       4010            ands    r0, r2
10
    8138:       4013            ands    r3, r2
11
    813a:       181b            adds    r3, r3, r0
12
    813c:       0918            lsrs    r0, r3, #4
13
    813e:       18c0            adds    r0, r0, r3
14
    8140:       4b05            ldr     r3, [pc, #20]   ; (8158 <__popcountsi2+0x30>)
15
    8142:       4003            ands    r3, r0
16
    8144:       0218            lsls    r0, r3, #8
17
    8146:       18c0            adds    r0, r0, r3
18
    8148:       0403            lsls    r3, r0, #16
19
    814a:       18c0            adds    r0, r0, r3
20
    814c:       0e00            lsrs    r0, r0, #24
21
    814e:       4770            bx      lr
22
    8150:       55555555        .word   0x55555555
23
    8154:       33333333        .word   0x33333333
24
    8158:       0f0f0f0f        .word   0x0f0f0f0f

von Patrick (Gast)


Lesenswert?

Ich würde mir für ein nibble (Werte 0 bis 15) ein Konstantes Array als 
Lookup-Tabelle machen. Dann hast du nur noch ein Viertel der 
Iterationen. Kannst das ganze auch auf ein ganzes Byte hochziehen, dann 
hätte deine Lookuptabelle 256 Einträge (mehr Speicher, mehr 
Geschwindigkeit) Die Tabelle würde ich global definieren, damit sie 
nicht jedes mal auf den Stack geschaufelt wird.

Vom Prinizp her:
1
cx=0;
2
vxkop =vx;
3
4
while (vxkop)
5
{
6
cx += bitspronibble [ vxkop & 0x0F ];
7
vxkop = vxkop >> 4;
8
}

von Rudolph R. (rudolph)


Lesenswert?

Harstad schrieb:
> Mein Problem: dafür, dass diese Funktion so simpel ist, ist sie auf
> einem mit 120 MHz getakteten ATSAMD unglaublich langsam und haut mir
> mein ganzes Timing durcheinander.

Ich habe das gerade mal ausprobiert auf einem ATSAME51J19A der auf 
120MHz läuft, also bis auf das der zusätzlich CAN hat der gleiche 
Controller.

Ich messe die Ausführungsgeschwindigkeit der Funktion mit einem Timer 
der auf dem internen RC-Oscillator läuft mit TC_CTRLA_PRESCALER_DIV2, 
also 24 MHz.

...
num_profile_a = parityOK(val_a, val_b);
...
val_a+=23;
val_b+=47;
val_a &= 0x0002ffffd;
val_b &= 0x0002ffffd;

Ein bisschen "Chaos" rein gefüttert damit das nicht weg optimiert wird.
Das "num_profile_a" ist in dem Zusammenhang einfach nur eine Variable 
die woanders auch noch benutzt und demnach nicht weg optimiert wird.

Die Funktion habe ich einfach so übernommen, das führt zu der Warnung:
"warning: always_inline function might not be inlinable [-Wattributes]"

Wie auch immer, das Ergebnis sind konstante 49 Timer-Ticks, oder auch 
2µs.
Oder auch 240 Taktzyklen bezogen auf die 120MHz Core-Takt.

Zu langsam mag ja gut sein, aber ist "unglaublich langsam" nicht doch 
ein klein wenig übertrieben?

von Rudolph R. (rudolph)


Lesenswert?

Okay, so braucht das 17 Timer-Ticks:
1
static __attribute__((always_inline)) char parityOK(const uint_fast32_t vx,const uint_fast32_t vy)
2
{
3
  uint_fast8_t  cx=0,cy=0;
4
  
5
  cx = __builtin_popcountl (vx & 0x003ffffc);
6
  cy = __builtin_popcountl (vy & 0x003ffffc);
7
  
8
  if ((vx & 0x00000003)!=(cx & 0x00000003))
9
  {
10
    return 0;
11
  }
12
  if ((vy & 0x00000003)!=(cy & 0x00000003))
13
  {
14
    return 0;
15
  }
16
  return 1;
17
}

von M. K. (Gast)


Lesenswert?

Du bist Dir aber sicher, das nicht irgendein IRQ da reingrätscht?
Wackel mal mit einem PIN in Deiner Schleife und seh Dir an wie konstant 
und wie lange der überhaupt darin herumwerkelt.
Ist überhaupt sichergestellt das diese Routine mit höchster Prio läuft 
und andere Prozesse unterbrechen kann?

Harstad schrieb:
> So langsam, dass es für meine Belange zu langsam ist.

Das ist keine Aussage.
Wie langsam und wie sind Deine Belange?
Schau Dir an was der Compiler daraus macht und zähl die Takte.
Ggf. ist das Softwaremäßig garnicht zu lösen.
Wir wissen ja nicht wohl Du hinwillst mit dem Timing und was der 
Rechenknecht noch alles tun muss.

von Egon D. (Gast)


Lesenswert?

M. K. schrieb:

> Schau Dir an was der Compiler daraus macht
> und zähl die Takte.

Nicht notwendig.

Er behandelt jedes Bit einzeln, d.h. es gibt
20 Durchläufe mit etwa 3 Befehlen je Durchlauf.

Vergleiche das mit Jörgs Assemblertext: Der
braucht 20 Befehle .

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Egon D. schrieb:
> Vergleiche das mit Jörgs Assemblertext

Nicht meiner, sondern der der libgcc. ;-) (Die wird hinter dem genannten 
__builtin_popcount() vom Compiler aufgerufen.)

von Rudolph R. (rudolph)


Lesenswert?

Jörg W. schrieb:
> Ergibt mit -mcpu=cortex-m0 -mthumb das hier:

Gerade erst gesehen, der Controller ist aber ein M4F.
Das kommt dabei raus wenn man nur "SAMD" schreibt und nicht die Nummer 
dazu.

von Egon D. (Gast)


Lesenswert?

Jörg W. schrieb:

> Egon D. schrieb:
>> Vergleiche das mit Jörgs Assemblertext
>
> Nicht meiner, sondern der der libgcc. ;-)

Ich weiss.
Du bist halt der Überbringer der Nachricht,
nicht der Verfasser :)

Die Routine ist übrigens ziemlich hoch optimiert;
besonders das Umschwenken auf die Addition der
8Bit-Gruppen am Ende finde ich elegant, das spart
nochmal ein paar Befehle.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rudolph R. schrieb:
> Jörg W. schrieb:
>> Ergibt mit -mcpu=cortex-m0 -mthumb das hier:
>
> Gerade erst gesehen, der Controller ist aber ein M4F.
> Das kommt dabei raus wenn man nur "SAMD" schreibt und nicht die Nummer
> dazu.

OK, hier nochmal mit -mcpu=cortex-m4 -mthumb. Wird kürzer. :)
1
0000812c <__popcountsi2>:
2
    812c:       0843            lsrs    r3, r0, #1
3
    812e:       f003 3355       and.w   r3, r3, #1431655765     ; 0x55555555
4
    8132:       1ac0            subs    r0, r0, r3
5
    8134:       0883            lsrs    r3, r0, #2
6
    8136:       f003 3333       and.w   r3, r3, #858993459      ; 0x33333333
7
    813a:       f000 3033       and.w   r0, r0, #858993459      ; 0x33333333
8
    813e:       4418            add     r0, r3
9
    8140:       eb00 1010       add.w   r0, r0, r0, lsr #4
10
    8144:       f000 300f       and.w   r0, r0, #252645135      ; 0xf0f0f0f
11
    8148:       eb00 2000       add.w   r0, r0, r0, lsl #8
12
    814c:       eb00 4000       add.w   r0, r0, r0, lsl #16
13
    8150:       0e00            lsrs    r0, r0, #24
14
    8152:       4770            bx      lr

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Egon D. schrieb:
> Die Routine ist übrigens ziemlich hoch optimiert;

Dafür ist es ja auch gut, wenn man für solcherlei Dinge Informatiker 
hat, die einem das als Library zur Verfügung stellen.

Der oben gegebene Hinweis auf __builtin_popcount() war daher m.E. der 
wirklich zielführendste im Thread.

(Zu Hause steht noch irgendwo das Buch "Hacker's Delight" rum, da sind 
solche Algorithmen für Standardaufgaben erklärt. Hatte ich vorhin nicht 
zur Hand.)

von Peter D. (peda)


Lesenswert?

https://en.wikipedia.org/wiki/Hamming_weight

Interessant ist popcount64d(), wenn nur wenige Bits gesetzt sind.

: Bearbeitet durch User
von Harstad (Gast)


Lesenswert?

Cool, danke, damit kann ich was anfangen :-)

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.