
|
Sie befinden sich hier: Elektronik / Codeoptimierung mit struct /
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-GrenzenC-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; 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) {
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 {
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) {
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 SeiteDiese Seite wurde archiviert, d.h. sie wird nicht mehr aktiv gepflegt und die Informationen entsprechen unter Umständen nicht mehr dem aktuellen Stand. |