(→Zusammenfassung) |
(→Zusammenfassung) |
||
(Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
Zeile 884: | Zeile 884: | ||
|<tt>sum_n_formel (20)</tt> || 19 || ca. 417 | |<tt>sum_n_formel (20)</tt> || 19 || ca. 417 | ||
|} | |} | ||
− | Die Formelversion ist in BASCOM so langsam, weil eine echte Division benutzt wird. | + | 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.