K (→Eine Variable hochzählen) |
(→Zusammenfassung) |
||
(50 dazwischenliegende Versionen von 8 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
Ein wichtiges Merkmal eines [[Compiler]]s ist die Güte des erzeugten Codes. | Ein wichtiges Merkmal eines [[Compiler]]s ist die Güte des erzeugten Codes. | ||
− | Immerhin | + | 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. |
+ | |||
+ | {{Ausbauwunsch|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. | 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. | ||
Zeile 6: | 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. Neben diesen für jedes Programm essenziellen Abschnitten | + | 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. | ||
Interessanter erscheint ein Vergleich einfacher Aufgaben, die erkennen lassen, wie gut ein Compiler in der Lage ist, die Ressourcen eines [[Mikrocontroller]]s zu nutzen bzw. zu schonen. | Interessanter erscheint ein Vergleich einfacher Aufgaben, die erkennen lassen, wie gut ein Compiler in der Lage ist, die Ressourcen eines [[Mikrocontroller]]s zu nutzen bzw. zu schonen. | ||
+ | |||
= Rahmenbedingungen = | = Rahmenbedingungen = | ||
Zeile 14: | Zeile 19: | ||
;[[avr-gcc]]: Die Assembler-Ausgaben wurden mit der Optimierungsstufe "auf Größe optimieren" (<tt>-Os</tt>) 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. | ;[[avr-gcc]]: Die Assembler-Ausgaben wurden mit der Optimierungsstufe "auf Größe optimieren" (<tt>-Os</tt>) 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. | ||
− | ;[[Bascom]]: | + | :Die acr-gcc Assembler-Dumps wurden erzeugt mit |
+ | :{| | ||
+ | |- | ||
+ | <pre> | ||
+ | > avr-gcc -mmcu=atmega8 -g -Os datei.c -c -o datei.o | ||
+ | > avr-objdump -d -S datei.o | ||
+ | </pre> | ||
+ | (Zu beachten ist, daß <tt>datei.o</tt> 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 = | = Summe der ersten ''n'' Zahlen = | ||
Zeile 62: | Zeile 79: | ||
| | | | ||
<pre> | <pre> | ||
− | + | 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 | ||
+ | |||
</pre> | </pre> | ||
}} | }} | ||
Zeile 71: | Zeile 105: | ||
| | | | ||
<pre> | <pre> | ||
− | sum_n_loop: | + | 00000000 <sum_n_loop>: |
− | ldi r18, | + | |
− | ldi r19, | + | unsigned int |
− | + | sum_n_loop (unsigned int n) | |
− | + | { | |
− | breq . | + | unsigned int sum = 0; |
− | add r18,r24 | + | 0: ldi r18, 0x00 |
− | adc r19,r25 | + | 2: ldi r19, 0x00 |
− | sbiw r24, | + | unsigned int i; |
− | rjmp . | + | |
− | + | for (i=n; i > 0; i--) | |
− | movw r24,r18 | + | 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 | ||
</pre> | </pre> | ||
− | + | ||
+ | das Ganze aus Spass auch noch als Keil-ARM7 Code | ||
+ | |||
<pre> | <pre> | ||
− | ??? | + | *** 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' | ||
</pre> | </pre> | ||
− | + | | | |
+ | <pre> | ||
+ | ;--------------------------------------------------------- | ||
+ | ; 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 | ||
+ | </pre> | ||
+ | '''Remark''': Das reine Register-rechnen (GCC) macht Bascom nicht. Er arbeitet immer im SRAM , brav mit Load & Store. (PicNick) | ||
+ | }} | ||
== Berechnung mit rekursiver Funktion == | == Berechnung mit rekursiver Funktion == | ||
Zeile 111: | 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 120: | Zeile 275: | ||
| | | | ||
<pre> | <pre> | ||
− | sum_n_rekursiv: | + | 00000000 <sum_n_rekursiv>: |
− | push r28 | + | |
− | push r29 | + | unsigned int |
− | movw r28,r24 | + | sum_n_rekursiv (unsigned int n) |
− | sbiw r24, | + | { |
− | breq . | + | 0: push r28 |
− | sbiw r24, | + | 2: push r29 |
− | rcall | + | 4: movw r28, r24 |
− | add r24,r28 | + | 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 | ||
+ | |||
+ | </pre> | ||
+ | daraus macht der Keil-ARM7 folgendes | ||
+ | <pre> | ||
+ | *** 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 | ||
</pre> | </pre> | ||
| | | | ||
Zeile 144: | Zeile 334: | ||
'''Quellcodes:''' | '''Quellcodes:''' | ||
− | |||
{{Codevergleich|avr-gcc|BASCOM | {{Codevergleich|avr-gcc|BASCOM | ||
− | | | + | | |
<pre> | <pre> | ||
unsigned int | unsigned int | ||
Zeile 154: | Zeile 343: | ||
} | } | ||
</pre> | </pre> | ||
− | | | + | | |
<pre> | <pre> | ||
− | + | Bascom mag keine Formeln. Diese müssen erst zerlegt werden. | |
+ | Ob diese Art der Berechnug schlechter ist kann so nicht gesagt | ||
+ | werden. Auf jedem Fall ist diese Methode nicht elegant und | ||
+ | ziemlich unübersichtlich, da der Zusammenhang verloren geht. | ||
+ | |||
+ | Declare Function Sum_n_formel(byval I As Integer) As Integer | ||
+ | |||
+ | Dim X As Integer | ||
+ | |||
+ | 'Start | ||
+ | |||
+ | X = Sum_n_formel(10) | ||
+ | End | ||
+ | |||
+ | ' ----- | ||
+ | |||
+ | Function Sum_n_formel(byval I As Integer) | ||
+ | |||
+ | X = I + 1 '(n+1) | ||
+ | X = X * I 'n * (n+1) | ||
+ | |||
+ | Sum_n_formel = X / 2 'n * (n+1) / 2 | ||
+ | |||
+ | End Function | ||
+ | |||
</pre> | </pre> | ||
}} | }} | ||
− | |||
{{Codevergleich|avr-gcc|BASCOM | {{Codevergleich|avr-gcc|BASCOM | ||
| | | | ||
<pre> | <pre> | ||
− | sum_n_formel: | + | 00000000 <sum_n_formel>: |
− | mul r24,r24 | + | |
− | movw r18,r0 | + | unsigned int |
− | mul r24,r25 | + | sum_n_formel (unsigned int n) |
− | add r19,r0 | + | { |
− | mul r25,r24 | + | return n*(n+1) / 2; |
− | add r19,r0 | + | 0: mul r24, r24 |
− | + | 2: movw r18, r0 | |
− | add r18,r24 | + | 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 | ||
+ | </pre> | ||
+ | Es geht auch mit deutlich weniger Registern im ARM7 | ||
+ | <pre> | ||
+ | *** 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> | ||
| | | | ||
<pre> | <pre> | ||
Zeile 207: | 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 238: | Zeile 485: | ||
reti | reti | ||
</pre> | </pre> | ||
+ | Variable holen, addieren, wegschreiben.. den Rest macht der ARM7 selbst. Viel interessanter ist, das der ARM den Counter in ein Register kriegt, der AVR aber über 2 Register rechnen muss (r24 und r25). Dafür muss aber der Arm 2 LDR ausführen um an die Countervariable zu kommen. | ||
+ | <pre> | ||
+ | *** CODE SEGMENT '?PR?ISR?A?main': | ||
+ | 288: count++; | ||
+ | 00 LDR R0,=count ; count | ||
+ | 04 LDR R1,[R0,#0x0] ; count | ||
+ | 08 ADD R1,R1,#0x0001 | ||
+ | 0C STR R1,[R0,#0x0] ; count | ||
+ | 10 BX R14 | ||
+ | </pre> | ||
+ | Der Code ist auf Grund der Architektur nicht direkt vergleichbar mit dem AVR. Der Arm 7 besitzt einen eigenen Interrupt Stack, Befehle um ganze Registersätze zu popen/puschen, einen Vectorinterruptcontroller und User/Supervisor Prioritätsebenen, ähnlich der einer x86 CPU. Das wäre als wolle man avr-gcc und Bascom vergleichen :) | ||
| | | | ||
<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 249: | Zeile 574: | ||
| | | | ||
<pre> | <pre> | ||
+ | #include <avr/io.h> | ||
+ | #include <avr/signal.h> | ||
+ | |||
extern void foo(); | extern void foo(); | ||
Zeile 306: | Zeile 634: | ||
reti | reti | ||
</pre> | </pre> | ||
+ | Und hier der Arm 7 Code. | ||
+ | <pre> | ||
+ | *** CODE SEGMENT '?PR?ISR?A?main': | ||
+ | 285: void ISR( void ) | ||
+ | 00 STMDB R13!,{LR} | ||
+ | 287: foo(); | ||
+ | 04 BL foo?A ; Targ=0x0 | ||
+ | 08 LDMIA R13!,{R3} | ||
+ | 0C BX R3 | ||
+ | </pre> | ||
+ | Der Code ist auf Grund der Architektur nicht direkt vergleichbar mit dem AVR. | ||
+ | Der Arm 7 besitzt einen eigenen Interrupt Stack, Befehle um ganze Registersätze zu popen/puschen, einen Vectorinterruptcontroller und User/Supervisor Prioritätsebenen, ähnlich der einer x86 CPU. | ||
+ | Das wäre als wolle man avr-gcc und Bascom vergleichen :) | ||
| | | | ||
<pre> | <pre> | ||
Zeile 369: | Zeile 710: | ||
| | | | ||
<pre> | <pre> | ||
− | bubble_sort: | + | 00000000 <bubble_sort>: |
− | + | { | |
− | + | uint8_t i; | |
− | movw r26,r22 | + | |
− | add | + | // Bereich schrumpft von n auf 2 |
− | adc | + | for (i = n; i >= 2; i--) |
− | sbiw r26, | + | 0: cpi r24, 0x02 |
− | + | 2: brcs .+54 ; 0x3a | |
− | ldi | + | 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 | ||
+ | } | ||
+ | </pre> | ||
+ | Und hier das Ganze noch mal als ARM7 Code | ||
+ | <pre> | ||
+ | *** CODE SEGMENT '?PR?bubble_sort?A?main': | ||
+ | 285: void bubble_sort (uint8_t n, char * a) | ||
+ | 00 STMDB R13!,{R4-R6} | ||
+ | 04 MOV R3,R0 ; n | ||
+ | 286: { | ||
+ | 290: for (i = n; i >= 2; i--) | ||
+ | 08 MOV R0,R3 ; n | ||
+ | 0C B L_11 ; Targ=0xD4 | ||
+ | 10 L_12: | ||
+ | 291: { | ||
+ | 292: char max = CHAR_MIN; | ||
+ | 10 MOV R3,#0x0 | ||
+ | 14 MOV R6,R3 ; max | ||
+ | 296: for (j = 0; j < i; j++) | ||
+ | 18 B L_16 ; Targ=0x64 | ||
+ | 1C L_17: | ||
+ | 298: if (a[j] >= max) | ||
+ | 1C MOV R4,R3 ; j | ||
+ | 20 MOV R5,R4,LSL #24 ; j | ||
+ | 24 MOV R5,R5,LSR #24 | ||
+ | 28 MOV R4,R1 ; a | ||
+ | 2C LDRB R4,[R4,+R5] | ||
+ | 30 MOV R5,R6 ; max | ||
+ | 34 MOV R5,R5,LSL #24 ; max | ||
+ | 38 MOV R5,R5,LSR #24 | ||
+ | 3C CMP R4,R5 | ||
+ | 40 BCC L_14 ; Targ=0x5C | ||
+ | 301: j_max = j; | ||
+ | 44 MOV R2,R3 ; j | ||
+ | 302: max = a[j_max]; | ||
+ | 48 MOV R4,R2 ; j_max | ||
+ | 4C MOV R5,R4,LSL #24 ; j_max | ||
+ | 50 MOV R5,R5,LSR #24 | ||
+ | 54 MOV R4,R1 ; a | ||
+ | 58 LDRB R6,[R4,+R5] | ||
+ | 304: } | ||
+ | 5C L_14: | ||
+ | 5C ADD R3,R3,#0x0001 ; j | ||
+ | 60 AND R3,R3,#0x00FF | ||
+ | 64 L_16: | ||
+ | 64 MOV R4,R0 ; i | ||
+ | 68 MOV R5,R4,LSL #24 ; i | ||
+ | 6C MOV R5,R5,LSR #24 | ||
+ | 70 MOVR R4,R3 ; j | ||
+ | 74 MOV R4,R4,LSL #24 ; j | ||
+ | 78 MOV R4,R4,LSR #24 | ||
+ | 7C CMP R4,R5 | ||
+ | 80 BCC L_17 ; Targ=0x1C | ||
+ | 307: a[j_max] = a[i-1]; | ||
+ | 84 MOV R3,R0 ; i | ||
+ | 88 MOV R4,R3,LSL #24 ; i | ||
+ | 8C MOV R4,R4,LSR #24 | ||
+ | 90 MOV R3,R1 ; a | ||
+ | 94 ADD R3,R3,R4 ; a | ||
+ | 98 LDRB R3,[R3,#0xFFFFFFFF] | ||
+ | 9C MOV R4,R2 ; j_max | ||
+ | A0 MOV R5,R4,LSL #24 ; j_max | ||
+ | A4 MOV R5,R5,LSR #24 | ||
+ | A8 MOV R4,R1 ; a | ||
+ | AC STRB R3,[R4,+R5] | ||
+ | 308: a[i-1] = max; | ||
+ | B0 MOV R3,R6 ; max | ||
+ | B4 MOV R4,R0 ; i | ||
+ | B8 MOV R5,R4,LSL #24 ; i | ||
+ | BC MOV R5,R5,LSR #24 | ||
+ | C0 MOV R4,R1 ; a | ||
+ | C4 ADD R4,R4,R5 ; a | ||
+ | C8 STRB R3,[R4,#0xFFFFFFFF] | ||
+ | 309: } | ||
+ | CC SUB R0,R0,#0x0001 ; i | ||
+ | D0 AND R0,R0,#0x00FF | ||
+ | D4 L_11: | ||
+ | D4 MOV R3,R0 ; i | ||
+ | D8 MOV R3,R3,LSL #24 ; i | ||
+ | DC MOV R3,R3,LSR #24 | ||
+ | E0 CMP R3,#0x0002 | ||
+ | E4 BGE L_12 ; Targ=0x10 | ||
+ | 310: } | ||
+ | E8 LDMIA R13!,{R4-R6} | ||
+ | EC BX R14 | ||
</pre> | </pre> | ||
| | | | ||
Zeile 412: | Zeile 855: | ||
Die Codes wurden etwas nachkommentiert und -formatiert, um besser den Zusammenhang mit der Quelle durchblicken zu können. | 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. | ||
+ | |||
+ | {| {{Blauetabelle}} | ||
+ | <!-------------------------------------------------------------------------------------> | ||
+ | |- {{Hintergrund1}} | ||
+ | ! Verbrauch an Flash (in Bytes) || avr-gcc || BASCOM | ||
+ | |- | ||
+ | |<tt>sum_n_loop</tt> || 20 || ca. 40 + 6 | ||
+ | |- | ||
+ | |<tt>sum_n_rekursiv</tt> || 24 ||ca. 188 ? | ||
+ | |- | ||
+ | |<tt>sum_n_formel</tt> || 26 || ca. 300 | ||
+ | |} | ||
+ | |||
+ | |||
+ | {| {{Blauetabelle}} | ||
+ | <!-------------------------------------------------------------------------------------> | ||
+ | |- {{Hintergrund1}} | ||
+ | ! Verbrauch an Zeit (in CPU-Zyklen) || avr-gcc || BASCOM | ||
+ | |- | ||
+ | |<tt>sum_n_loop (20)</tt> || 191 || ca. 1355 | ||
+ | |- | ||
+ | |<tt>sum_n_rekursiv (20)</tt> || 477 || ca. 2058 | ||
+ | |- | ||
+ | |<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= | ||
Zeile 418: | Zeile 891: | ||
* [[Bascom]] | * [[Bascom]] | ||
* [[AVR]] | * [[AVR]] | ||
+ | * [[ARM]] | ||
[[Kategorie:Quellcode Bascom]] | [[Kategorie:Quellcode Bascom]] | ||
[[Kategorie:Quellcode C]] | [[Kategorie:Quellcode C]] | ||
− | [[Kategorie:Quellcode Assembler]] | + | [[Kategorie:Quellcode Assembler AVR]] |
[[Kategorie:Software]] | [[Kategorie:Software]] | ||
[[Kategorie:Praxis]] | [[Kategorie:Praxis]] |
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.