Codeoptimierung für AVR-Prozessoren

 Startseite | Blog | Sitemap | Impressum | Login

Bei der AVR-Serie von Atmel handelt es sich um 8-bit Mikrocontroller. Da diese einfach in C zu programmieren sind, stört das praktisch nicht, da der Compiler dafür sorgt, dass man auch mit grösseren Datentypen arbeiten kann, ohne an die Hardware zu denken. Das kann aber ziemlich schief gehen, wenn man nicht genau weiss, wie der C-Compiler den Code übersetzt. Grundsätzlich arbeitet der GCC-Compiler für den AVR gut und erzeugt sehr gut optimierten Code (z.B. werden Variablen werden soweit wie möglich in Registern gespreichert). An einigen Stellen funktioniert die Optimierung aber nicht vollautomatisch und dann kann es vorkommen, dass der Code extrem langsam wird.

Shift-Operationen über Byte-Grenzen

C-Programmierer sind es gewöhnt, bei Divisionen oder Multiplikationen mit Zweierpotenzen nicht den Multiplikations- oder Divisionsoperator zu nutzen, sondern die Bit-Shift-Operatoren << und >>. Die die meisten Prozessoren diese direkt in Maschinencode kennen, sind sie sehr effizient. Auf einem 8-bit-Prozessor muss das aber nicht zwangsläufig richtig sein.

Ich will das an einer einfachen Aufgabe demonstrieren. Aus einem 32-bit-Wert werde die obersten 10 Bits in einem 16-bit Integer gespeichert werden. Es soll also um 22 Bit nach recht geschoben werden.

Das ist übrigens kein exotisches Beispiel, ich brauchte es selbst für einen DDS-Generator.

 

Muss man sich überhaupt Gedanken um Bit-Shift-Operatoren machen? Nicht unbedingt, dann hier optimiert GCC recht gut. Die Codezeilen

    res=val/4194304;
    res=val >> 22;

sind nicht nur semantisch gleich, sondern werden vom AVR-GCC auch in identischen Maschinencode übersetzt. Der Compiler erkennt also, dass die obere Division "einfach" durch Bitshift-Operationen zu erledigen ist.

Aber wie sieht der ezeugte Maschinencode aus? Genau so:

void version1(uint32_t val) {
  92:    26 e1           ldi    r18, 0x16    ; 22
  94:    96 95           lsr    r25
  96:    87 95           ror    r24
  98:    77 95           ror    r23
  9a:    67 95           ror    r22
  9c:    2a 95           dec    r18
  9e:    d1 f7           brne    .-12         ; 0x94 <version1+0x2>
  a0:    70 93 61 00     sts    0x0061, r23
  a4:    60 93 60 00     sts    0x0060, r22
  a8:    08 95           ret
}

Die letzten beiden sts-Befehle dienen nur zur Speicherung des Ergebnisses und haben mit der eigentlichen Berechnung nichts zu tun.

Eigentlich sieht der Code recht kompakt aus. Wenn man aber genau schaut, sieht man, dass eine Schleife mit 6 Befehlen ganze 22 Mal durchlaufen wird. Das macht für die eigentliche Berechung 22x6+1 = 133 Takte! Das ist definitiv nicht effizient.

Ein Problem des vorherigen Codes besteht darin, dass hier fleissig auf 32 Bit Breite gearbeitet wird, obwohl die unteren 16 Bit für das Ergebnis völlig belanglos sind. Der Compiler erkennt das leider nicht, also muss man ihm etwas auf die Sprünge helfen:

typedef union {
 uint32_t value32;
 struct {
     uint16_t l;
    uint16_t h;
 } value16;
} int32;

void version3(uint32_t val) {
    int32 x;
    x.value32 = val;

    res=x.value16.h >> (22-16);
}

Der Weg über eine zusätzliche temporäre Variable sieht eigentlich nicht gerade effizient aus. Aber der Compiler ist schlau genug, diese wegzuoptimieren:

void version3(uint32_t val) {
  c2:    00 24           eor    r0, r0
  c4:    88 0f           add    r24, r24
  c6:    99 1f           adc    r25, r25
  c8:    00 1c           adc    r0, r0
  ca:    88 0f           add    r24, r24
  cc:    99 1f           adc    r25, r25
  ce:    00 1c           adc    r0, r0
  d0:    89 2f           mov    r24, r25
  d2:    90 2d           mov    r25, r0
  d4:    90 93 61 00     sts    0x0061, r25
  d8:    80 93 60 00     sts    0x0060, r24
  dc:    08 95           ret
}

Dieser Programmcode braucht zwar im Flash ein paar Bytes mehr, das ist aber meist wenig störend. Wieviele Befehle werden hier für die gleiche Berechnung gebraucht? Ganze 9 Takte. Das ist zur vorherigen Version eine Verbesserung um mehr als das 10-fache!

Archivierte Seite

Diese Seite wurde archiviert, d.h. sie wird nicht mehr aktiv gepflegt und die Informationen entsprechen unter Umständen nicht mehr dem aktuellen Stand.

Werbung
Look-Out
Talking about everything
Crazy audio
DIY audio projects and more
Anmesty International SchweizMenschenrechte für alle

Menschen für MenschenKarlheinz Böhms Äthiopienhilfe