Ein wichtiges Merkmal eines Compilers ist die Güte des erzeugten Codes.
Immerhin will man seine Hardware optimal nutzen, 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.
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.
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 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.
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:
avr-gcc
unsigned int
sum_n_loop (unsigned int n)
{
unsigned int sum = 0;
unsigned int i;
for (i=n; i > 0; i--)
sum += i;
return sum;
}
|
|
|
BASCOM
Declare Function Sum_n_loop(byval N As Integer) As Integer
Dim X As Integer
'Start
X = Sum_n_loop(10)
End
' -----
Function Sum_n_loop(byval N As Integer) As Integer
Sum_n_loop = 0
While N > 0
Sum_n_loop = Sum_n_loop + N
Decr N
Wend
End Function
|
|
Compilat:
avr-gcc
00000000 <sum_n_loop>:
unsigned int
sum_n_loop (unsigned int n)
{
unsigned int sum = 0;
0: ldi r18, 0x00
2: ldi r19, 0x00
unsigned int i;
for (i=n; i > 0; i--)
4: sbiw r24, 0x00
6: breq .+8 ; 0x10
sum += i;
8: add r18, r24
a: adc r19, r25
c: sbiw r24, 0x01
e: rjmp .-12 ; 0x4
return sum;
}
10: movw r24, r18
12: ret
das Ganze aus Spass auch noch als Keil-ARM7 Code
*** CODE SEGMENT '?PR?sum_n_loop?A?main':
283: unsigned int sum_n_loop (unsigned int n)
00 MOV R2,R0 ; n
284: {
285: unsigned int sum = 0;
04 MOV R1,#0x0
288: for (i=n; i > 0; i--)
08 MOV R0,R2 ; n
0C B L_11 ; Targ=0x1C
10 L_12:
289: sum += i;
10 MOV R2,R0 ; i
14 ADD R1,R1,R2 ; sum
18 SUB R0,R0,#0x0001 ; i
1C L_11:
1C MOV R2,R0 ; i
20 CMP R2,#0x0000 ; i
24 BHI L_12 ; Targ=0x10
291: return sum;
28 MOV R0,R1 ; sum
292: }
2C BX R14
30 ENDP ; 'sum_n_loop?A'
|
|
|
BASCOM
;---------------------------------------------------------
; Function Sum_n_loop(byval N As Integer) As Integer
;---------------------------------------------------------
L_0x00B4:
;---------------------------------------------------------
; Sum_n_loop = 0
;---------------------------------------------------------
LDI r24,0x00 ; clear
LDI r25,0x00 ;
LDD XL,Y + 2 ; return value addr (softstack)
LDD XH,Y + 3
ST X+,r24 ;
ST X,r25
L_0x00C0:
;---------------------------------------------------------
; While N > 0
;---------------------------------------------------------
LDD XL,Y + 0 ; argument addr (softstack)
LDD XH,Y + 1
LD r16,X+ ; load r16:r17
LD r17,X
CPI r16,0x00 ; LOW <> 0 ?
LDI r21,0x00
CPC r17,r21 ; HIGH <> 0 ?
BRLT L_0x00D6 ; branch lower ->function exit
BREQ L_0x00D6 ; branch equal ->function exit
JMP L_0x00DA ; continue
L_0x00D6:
JMP L_0x0102 ; jmp function exit
L_0x00DA:
;---------------------------------------------------------
; Sum_n_loop = Sum_n_loop + N
;---------------------------------------------------------
LDD XL,Y + 2 ; summ_n_loop
LDD XH,Y + 3
LD r16,X+ ; into r16:r17
LD r17,X
LDD XL,Y + 0 ; N
LDD XH,Y + 1
LD r20,X+ ; into r20:r21
LD r21,X
ADD r16,r20 ; add r16, r20
ADC r17,r21 ; add+cy r17, r21
LDD XL,Y + 2 ;
LDD XH,Y + 3
ST X+,r16 ; store sum
ST X,r17
;---------------------------------------------------------
; Decr N
;---------------------------------------------------------
LDD XL,Y + 0
LDD XH,Y + 1
CALL L_0x0114
;---------------------------------------------------------
; Wend
;---------------------------------------------------------
JMP L_0x00C0 ; reenter loop
;---------------------------------------------------------
L_0x0102:
RET ; end function
;---------------------------------------------------------
; library function : decrement integer
;---------------------------------------------------------
L_0x0114:
LD ZL,X+
LD ZH,X
SBIW ZL,0x0001
ST X,ZH
ST -X,ZL
RET
Remark: Das reine Register-rechnen (GCC) macht Bascom nicht. Er arbeitet immer im SRAM , brav mit Load & Store. (PicNick)
|
|
Berechnung mit rekursiver Funktion
Quellcodes:
avr-gcc
unsigned int
sum_n_rekursiv (unsigned int n)
{
if (n == 0)
return 0;
return n + sum_n_rekursiv (n-1);
}
|
|
|
|
Compilat:
avr-gcc
00000000 <sum_n_rekursiv>:
unsigned int
sum_n_rekursiv (unsigned int n)
{
0: push r28
2: push r29
4: movw r28, r24
if (n == 0)
6: sbiw r24, 0x00
8: breq .+8 ; 0x12
return 0;
return n + sum_n_rekursiv (n-1);
a: sbiw r24, 0x01
c: rcall .-14 ; 0x0
e: add r24, r28
10: adc r25, r29
}
12: pop r29
14: pop r28
16: ret
daraus macht der Keil-ARM7 folgendes
*** CODE SEGMENT '?PR?sum_n_rekursiv?A?main':
283: unsigned int sum_n_rekursiv (unsigned int n)
00 STMDB R13!,{R4,LR}
04 MOV R4,R0 ; n
285: if (n == 0)
08 MOV R0,R4 ; n
0C CMP R0,#0x0000 ; n
10 BNE L_9 ; Targ=0x1C
286: return 0;
14 MOV R0,#0x0
18 B L_10 ; Targ=0x30
1C L_9:
288: return n + sum_n_rekursiv (n-1);
1C MOV R0,R4 ; n
20 SUB R0,R0,#0x0001 ; n
24 BL sum_n_rekursiv?A ; Targ=0x0
28 MOV R1,R4 ; n
2C ADD R0,R1,R0 ; n
289: }
30 L_10:
30 LDMIA R13!,{R4}
34 LDMIA R13!,{R3}
38 BX R3
|
|
|
|
Berechnung durch Formel
Quellcodes:
avr-gcc
unsigned int
sum_n_formel (unsigned int n)
{
return n*(n+1) / 2;
}
|
|
|
|
avr-gcc
00000000 <sum_n_formel>:
unsigned int
sum_n_formel (unsigned int n)
{
return n*(n+1) / 2;
0: mul r24, r24
2: movw r18, r0
4: mul r24, r25
6: add r19, r0
8: mul r25, r24
a: add r19, r0
c: eor r1, r1
e: add r18, r24
10: adc r19, r25
}
12: movw r24, r18
14: lsr r25
16: ror r24
18: ret
Es geht auch mit deutlich weniger Registern im ARM7
*** CODE SEGMENT '?PR?sum_n_formel?A?main':
284: sum_n_formel (unsigned int n)
00 MOV R2,R0 ; n
286: return n*(n+1) / 2;
04 MOV R1,R2 ; n
08 ADD R1,R1,#0x0001 ; n
0C MOV R0,R2 ; n
10 MUL R0,R1,R0
14 MOV R0,R0,LSR #1
287: }
18 BX R14
|
<pre>
???
|
|
|
|
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:
avr-gcc
#include <avr/io.h>
#include <avr/signal.h>
int volatile count;
SIGNAL (SIG_OVERFLOW0)
{
count++;
}
|
|
|
|
Compilat:
avr-gcc
__vector_9:
push __zero_reg__
push __tmp_reg__
in __tmp_reg__,__SREG__
push __tmp_reg__
clr __zero_reg__
push r24
push r25
lds r24,count
lds r25,(count)+1
adiw r24,1
sts (count)+1,r25
sts count,r24
pop r25
pop r24
pop __tmp_reg__
out __SREG__,__tmp_reg__
pop __tmp_reg__
pop __zero_reg__
reti
|
|
|
|
Eine Funktion aufrufen
Quellcodes:
avr-gcc
#include <avr/io.h>
#include <avr/signal.h>
extern void foo();
SIGNAL (SIG_OVERFLOW1)
{
foo();
}
|
|
|
|
Compilat:
avr-gcc
__vector_8:
push __zero_reg__
push __tmp_reg__
in __tmp_reg__,__SREG__
push __tmp_reg__
clr __zero_reg__
push r18
push r19
push r20
push r21
push r22
push r23
push r24
push r25
push r26
push r27
push r30
push r31
rcall foo
pop r31
pop r30
pop r27
pop r26
pop r25
pop r24
pop r23
pop r22
pop r21
pop r20
pop r19
pop r18
pop __tmp_reg__
out __SREG__,__tmp_reg__
pop __tmp_reg__
pop __zero_reg__
reti
|
|
|
|
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:
avr-gcc
#include <limits.h>
#include <inttypes.h>
void
bubble_sort (uint8_t n, char * a)
{
uint8_t i;
// Bereich schrumpft von n auf 2
for (i = n; i >= 2; i--)
{
char max = CHAR_MIN;
uint8_t j, j_max;
// im Bereich nach MAX suchen
for (j = 0; j < i; j++)
{
if (a[j] >= max)
{
// MAX und Pos. merken
j_max = j;
max = a[j_max];
}
}
// MAX ans Ende tauschen
a[j_max] = a[i-1];
a[i-1] = max;
}
}
|
|
|
|
Compilat:
avr-gcc
00000000 <bubble_sort>:
{
uint8_t i;
// Bereich schrumpft von n auf 2
for (i = n; i >= 2; i--)
0: cpi r24, 0x02
2: brcs .+54 ; 0x3a
4: movw r26, r22
6: add r26, r24
8: adc r27, r1
a: sbiw r26, 0x01
{
char max = CHAR_MIN;
c: ldi r20, 0x80
uint8_t j, j_max;
// im Bereich nach MAX suchen
for (j = 0; j < i; j++)
e: ldi r19, 0x00
10: cp r19, r24
12: brcc .+18 ; 0x26
14: movw r30, r22
{
if (a[j] >= max)
16: ld r18, Z+
18: cp r18, r20
1a: brlt .+4 ; 0x20
{
// MAX und Pos. merken
j_max = j;
1c: mov r21, r19
max = a[j_max];
1e: mov r20, r18
20: subi r19, 0xFF
22: cp r19, r24
24: brcs .-16 ; 0x16
}
}
// MAX ans Ende tauschen
a[j_max] = a[i-1];
26: movw r30, r22
28: add r30, r21
2a: adc r31, r1
2c: ld r18, X
2e: st Z, r18
a[i-1] = max;
30: st X, r20
32: subi r24, 0x01
34: sbiw r26, 0x01
36: cpi r24, 0x02
38: brcc .-46 ; 0xc
3a: ret
}
|
|
|
|
Die Codes wurden etwas nachkommentiert und -formatiert, um besser den Zusammenhang mit der Quelle durchblicken zu können.
Siehe auch