Holzi (Diskussion | Beiträge) |
(→Zusammenfassung) |
||
(10 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
Zeile 8: | Zeile 8: | ||
Dieser Artikel versucht ansatzweise einen Codevergleich weit verbreiteter [[AVR]]-Compiler anhand sehr einfacher Aufgaben, die "geradeaus" und ohne Umschweife programmiert wurden. | Dieser Artikel versucht ansatzweise einen Codevergleich weit verbreiteter [[AVR]]-Compiler anhand sehr einfacher Aufgaben, die "geradeaus" und ohne Umschweife programmiert wurden. | ||
− | Ein Vergleich der Programmierung von Hardware-Komponenten und Peripherie wie [[Timer]]-, [[UART]]- oder [[I2C]]-Module scheint dabei weniger interessant, denn obwohl die Codes zum Steuern dieser Komponente in unterschiedlichen Sprachen recht verschieden aussehen, werden sie doch auf die selben Maschinen-Codes abgebildet, die sich im wesentlichen auf das Setzen und Lesen von Registern (SFRs) reduzieren. | + | Ein Vergleich der Programmierung von Hardware-Komponenten und Peripherie wie [[Timer]]-, [[UART]]- oder [[I2C]]-Module scheint dabei weniger interessant, denn obwohl die Codes zum Steuern dieser Komponente in unterschiedlichen Sprachen recht verschieden aussehen, werden sie doch auf die selben Maschinen-Codes abgebildet, die sich im wesentlichen auf das Setzen und Lesen von Registern (SFRs) reduzieren. Der BASCOM Compiler hat allerdings gerade bei IO Aufgaben seine Stärken, wenn dafür vorgefertigte Funktionen existieren. |
Neben diesen für jedes Programm essenziellen Abschnitten besteht ein Programm aber zum großen Teil aus hardwareunabhängigen Aufgaben wie Registerverwaltung, Funktionsaufrufen, Schleifen, Abfragen, Zuweisungen, Parameterübergaben, Abfragen von Bedingungen, Arithmetik, Implementierung von [[Interrupt Service Routine]]n, etc. | Neben diesen für jedes Programm essenziellen Abschnitten besteht ein Programm aber zum großen Teil aus hardwareunabhängigen Aufgaben wie Registerverwaltung, Funktionsaufrufen, Schleifen, Abfragen, Zuweisungen, Parameterübergaben, Abfragen von Bedingungen, Arithmetik, Implementierung von [[Interrupt Service Routine]]n, etc. | ||
Zeile 257: | Zeile 257: | ||
| | | | ||
<pre> | <pre> | ||
− | + | Function Summe(byval N As Integer) As Integer | |
+ | Local M As Integer | ||
+ | M = 0 | ||
+ | If N > 1 Then | ||
+ | N = N - 1 | ||
+ | M = Summe(n ) | ||
+ | End If | ||
+ | Summe = N + M | ||
+ | End Function | ||
</pre> | </pre> | ||
+ | Damit das Programm auch richtig läuft muß der Stackbereich deutlich erweitert werden. | ||
}} | }} | ||
Zeile 431: | Zeile 440: | ||
| | | | ||
<pre> | <pre> | ||
+ | $regfile = "m8def.dat" | ||
+ | |||
+ | Dim Count As Integer | ||
+ | |||
+ | On Int0 Int0_int ' Initialise the INT0 Interrupt | ||
+ | Enable Int0 ' enable the interrupt | ||
+ | Enable Interrupts | ||
+ | |||
+ | Do | ||
+ | Loop | ||
+ | |||
+ | Int0_int: ' The interrupt Handler for Int0 | ||
+ | Incr Count | ||
+ | Return | ||
</pre> | </pre> | ||
}} | }} | ||
Zeile 475: | Zeile 498: | ||
| | | | ||
<pre> | <pre> | ||
+ | PUSH R0 ;Push register on stack | ||
+ | PUSH R1 | ||
+ | PUSH R2 | ||
+ | PUSH R3 | ||
+ | PUSH R4 | ||
+ | PUSH R5 | ||
+ | PUSH R7 | ||
+ | PUSH R10 | ||
+ | PUSH R11 | ||
+ | PUSH R16 | ||
+ | PUSH R17 | ||
+ | PUSH R18 | ||
+ | PUSH R19 | ||
+ | PUSH R20 | ||
+ | PUSH R21 | ||
+ | PUSH R22 | ||
+ | PUSH R23 | ||
+ | PUSH R24 | ||
+ | PUSH R25 | ||
+ | PUSH R26 | ||
+ | PUSH R27 | ||
+ | PUSH R28 | ||
+ | PUSH R29 | ||
+ | PUSH R30 | ||
+ | PUSH R31 | ||
+ | IN R24,0x3F ; get SREG | ||
+ | PUSH R24 | ||
+ | LDI R26,0x00 ;Load immediate | ||
+ | LDI R27,0x01 ;Load immediate | ||
+ | RCALL PC+0x001D ;Relative call sub_inc: | ||
+ | POP R24 ;Pop register from stack | ||
+ | OUT 0x3F,R24 ;Out SREG | ||
+ | POP R31 | ||
+ | POP R30 | ||
+ | POP R29 | ||
+ | POP R28 | ||
+ | POP R27 | ||
+ | POP R26 | ||
+ | POP R25 | ||
+ | POP R24 | ||
+ | POP R23 | ||
+ | POP R22 | ||
+ | POP R21 | ||
+ | POP R20 | ||
+ | POP R19 | ||
+ | POP R18 | ||
+ | POP R17 | ||
+ | POP R16 | ||
+ | POP R11 | ||
+ | POP R10 | ||
+ | POP R7 | ||
+ | POP R5 | ||
+ | POP R4 | ||
+ | POP R3 | ||
+ | POP R2 | ||
+ | POP R1 | ||
+ | POP R0 | ||
+ | RETI | ||
+ | |||
+ | sub_inc: | ||
+ | LD R30,X+ ;Load indirect and postincrement | ||
+ | LD R31,X ;Load indirect | ||
+ | SUBI R30,0xFF ;Subtract immediate | ||
+ | SBCI R31,0xFF ;Subtract immediate with carry | ||
+ | ST X,R31 ;Store indirect | ||
+ | ST -X,R30 ;Store indirect and predecrement | ||
+ | RET ;Subroutine return | ||
</pre> | </pre> | ||
}} | }} | ||
Zeile 777: | Zeile 867: | ||
|<tt>sum_n_loop</tt> || 20 || ca. 40 + 6 | |<tt>sum_n_loop</tt> || 20 || ca. 40 + 6 | ||
|- | |- | ||
− | |<tt>sum_n_rekursiv</tt> || 24 || | + | |<tt>sum_n_rekursiv</tt> || 24 ||ca. 188 ? |
|- | |- | ||
− | |<tt>sum_n_formel</tt> || 26 || | + | |<tt>sum_n_formel</tt> || 26 || ca. 300 |
|} | |} | ||
Zeile 788: | Zeile 878: | ||
! Verbrauch an Zeit (in CPU-Zyklen) || avr-gcc || BASCOM | ! Verbrauch an Zeit (in CPU-Zyklen) || avr-gcc || BASCOM | ||
|- | |- | ||
− | |<tt>sum_n_loop (20)</tt> || 191 || | + | |<tt>sum_n_loop (20)</tt> || 191 || ca. 1355 |
|- | |- | ||
− | |<tt>sum_n_rekursiv (20)</tt> || 477 || | + | |<tt>sum_n_rekursiv (20)</tt> || 477 || ca. 2058 |
|- | |- | ||
− | |<tt>sum_n_formel (20)</tt> || 19 || | + | |<tt>sum_n_formel (20)</tt> || 19 || ca. 417 |
|} | |} | ||
+ | Die Formelversion ist in BASCOM so langsam, weil eine echte Division benutzt wird. Bei so einer kurzen Schleife sind die Vorteile durch die Optimierung von GCC besonders groß. Bei anderem Code wird der Unterschied eher kleiner ausfallen. Die Code-Größe enthält zum Teil vom Compiler bereitgestellte Unterroutinen (z.B. für die Division). Bei einem längeren Code taucht dieser Teil nur einmal auf. Die 2. Division wird entsprechend viel kürzer als die erste. | ||
=Siehe auch= | =Siehe auch= |
Aktuelle Version vom 5. Juli 2011, 19:55 Uhr
Ein wichtiges Merkmal eines Compilers ist die Güte des erzeugten Codes. Immerhin soll die Hardware optimal genutzt werden, und die geschriebenen Programme sollen möglichst wenig Laufzeit brauchen und möglichst wenig Speicher – also RAM und Flash – belegen.
Dieser Artikel ist noch lange nicht vollständig. Der Auto/Initiator hofft das sich weitere User am Ausbau des Artikels beteiligen.
Das Ergänzen ist also ausdrücklich gewünscht! Besonders folgende Dinge würden noch fehlen: Bascom Beispiele |
Ein Vergleich der erzeugten Codes ist jedoch nicht einfach, denn ein Problem kann bereits innerhalb ein und der selben Programmiersprache auf sehr unterschiedliche Art und Weisen formuliert oder gelöst werden.
Dieser Artikel versucht ansatzweise einen Codevergleich weit verbreiteter AVR-Compiler anhand sehr einfacher Aufgaben, die "geradeaus" und ohne Umschweife programmiert wurden.
Ein Vergleich der Programmierung von Hardware-Komponenten und Peripherie wie Timer-, UART- oder I2C-Module scheint dabei weniger interessant, denn obwohl die Codes zum Steuern dieser Komponente in unterschiedlichen Sprachen recht verschieden aussehen, werden sie doch auf die selben Maschinen-Codes abgebildet, die sich im wesentlichen auf das Setzen und Lesen von Registern (SFRs) reduzieren. Der BASCOM Compiler hat allerdings gerade bei IO Aufgaben seine Stärken, wenn dafür vorgefertigte Funktionen existieren.
Neben diesen für jedes Programm essenziellen Abschnitten besteht ein Programm aber zum großen Teil aus hardwareunabhängigen Aufgaben wie Registerverwaltung, Funktionsaufrufen, Schleifen, Abfragen, Zuweisungen, Parameterübergaben, Abfragen von Bedingungen, Arithmetik, Implementierung von Interrupt Service Routinen, etc.
Interessanter erscheint ein Vergleich einfacher Aufgaben, die erkennen lassen, wie gut ein Compiler in der Lage ist, die Ressourcen eines Mikrocontrollers zu nutzen bzw. zu schonen.
Inhaltsverzeichnis
Rahmenbedingungen
- avr-gcc
- Die Assembler-Ausgaben wurden mit der Optimierungsstufe "auf Größe optimieren" (-Os) für einen ATmega8 erstellt. GCC-Version war 3.4.x. Andere Optimierungsstufen haben wenig bis keinen Einfluss auf den erzeugtenCode. Codes für andere Controller der ATmega-Familie unterscheiden sich praktisch nicht vom ATmega8-Code.
- Die acr-gcc Assembler-Dumps wurden erzeugt mit
> avr-gcc -mmcu=atmega8 -g -Os datei.c -c -o datei.o > avr-objdump -d -S datei.o
(Zu beachten ist, daß datei.o ein noch nicht loktiertes Objekt ist und daher Adressen noch mit Platzhaltern (üblicherweise 0) gefüllt sind und daher im Dump noch als 0 angezeigt werden.)
- Bascom
- Das einzige Assembler-Code-Listing von Bascom (Aufsummieren in einer Schleife) wurde von PicNick mit einem selbstgestrickten *.HEX File Analyzer erstellt, da der Disassembler von AVR-Studio etwas schwer lesbar ist.
- Keil-ARM7
- Es wurden einige weitere Codezeilen für ein RISC ARM7 System (LPC2138) mit Keil Compiler jeweils unter die gcc-avr Varianten gestellt um auch hier einen Vergleich außerhalb der Konkurrenz gcc-avr/Bascom ziehen zu können. Die Optimierung lag auf 'speed' und Registeroptimierung. Man sieht gewisse Ähnlichkeiten zwischen gcc-avr und Keil-Arm7, im Detail sind die Aufgaben jedoch unterschiedlich gelöst. Auf Grund von deutlichen Unterschieden z.B. im Interrupt Handling sind die Codebeispiele teilweise nicht 1:1 vergleichbar. Die Codebeispiele sind ebenfalls noch nicht loktierte / gelinkte Objekte. Siehe oben.
Summe der ersten n Zahlen
Berechnet wird die Summe der ersten n Zahlen:
- [math] \operatorname{sum}(n) \,=\, \sum_{k=1}^n k \,=\, 1 + 2 + \ldots + n [/math]
Die Zahl n wird als 16-Bit Zahl angegeben und das Ergebnis als 16-Bit-Zahl berechnet. Ein eventueller Überlauf wird nicht beachtet.
Der Code wird jeweils als eigene Funktion implementiert, um Abhängigkeiten vom umliegenden Code zu vermeiden.
Für diese Berechnung gibt es mehrere Möglichkeiten.
Aufsummieren in einer Schleife
Quellcodes:
|
|
Compilat:
|
|
Berechnung mit rekursiver Funktion
Quellcodes:
|
|
Compilat:
|
|
Berechnung durch Formel
Quellcodes:
|
|
|
|
Interrupt-Routinen
Auch diese Beispiele machen nicht viel. Das erste zählt nur eine 16-Bit Variable hoch, das zweite macht nichts weiter, als ein Funktionsaufruf.
Eine Variable hochzählen
Quellcodes:
|
|
Compilat:
|
|
Eine Funktion aufrufen
Quellcodes:
|
|
Compilat:
|
|
Sortieren mit Bubble-Sort
Zum Abschluss noch ein komplexeres Beispiel: Ein Array mit 8-Bit-Werten soll mit Bubble-Sort der Größe nach sortiert werden.
Im Array wird nach dem größten Wert gesucht und dieser ans Ende getauscht. Danach macht man den Teilbereich, in dem man das Maximum sucht, um 1 kleiner, bis man fertig ist. Die größten Zahlen wandern wie Blasen nach oben, daher der Name Bubble-Sort für diesen Sortier-Algorithmus.
Variablen:
- n Array-Größe
- a das Array
- i Größe der Teilbereichs
- j Laufvariable durch den Teilbereich
Quellcodes:
|
|
Compilat:
|
|
Die Codes wurden etwas nachkommentiert und -formatiert, um besser den Zusammenhang mit der Quelle durchblicken zu können.
Zusammenfassung
Für diese drei Summier-Routinen erhält man folgende Ergebnisse für Laufzeit und Platzverbrauch. Die Laufzeittest wurden gemacht für N=20, d.h. es wurden die ersten 20 ganzen Zahlen aufsummiert.
Verbrauch an Flash (in Bytes) | avr-gcc | BASCOM |
---|---|---|
sum_n_loop | 20 | ca. 40 + 6 |
sum_n_rekursiv | 24 | ca. 188 ? |
sum_n_formel | 26 | ca. 300 |
Verbrauch an Zeit (in CPU-Zyklen) | avr-gcc | BASCOM |
---|---|---|
sum_n_loop (20) | 191 | ca. 1355 |
sum_n_rekursiv (20) | 477 | ca. 2058 |
sum_n_formel (20) | 19 | ca. 417 |
Die Formelversion ist in BASCOM so langsam, weil eine echte Division benutzt wird. Bei so einer kurzen Schleife sind die Vorteile durch die Optimierung von GCC besonders groß. Bei anderem Code wird der Unterschied eher kleiner ausfallen. Die Code-Größe enthält zum Teil vom Compiler bereitgestellte Unterroutinen (z.B. für die Division). Bei einem längeren Code taucht dieser Teil nur einmal auf. Die 2. Division wird entsprechend viel kürzer als die erste.