Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

(Aufsummieren in einer Schleife)
(Zusammenfassung)
 
(46 dazwischenliegende Versionen von 7 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 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.
+
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, besteht ein Programm aber zum großen Teil aus hardwareunabhängigen Aufgaben wie Registerverwaltung, Funktionsaufrufen, Schleifen, Abfragen, Zuweisungen, Parameterübergaben, Abfragen, Arithmetik, etc.
+
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 88: Zeile 105:
 
|
 
|
 
<pre>
 
<pre>
sum_n_loop:
+
00000000 <sum_n_loop>:
   ldi r18,lo8(0)
+
 
   ldi r19,hi8(0)
+
unsigned int
.L12:
+
sum_n_loop (unsigned int n)
  sbiw r24,0
+
{
   breq .L11
+
   unsigned int sum = 0;
   add r18,r24
+
  0: ldi r18, 0x00
   adc r19,r25
+
   2: ldi r19, 0x00
   sbiw r24,1
+
  unsigned int i;
   rjmp .L12
+
 
.L11:
+
  for (i=n; i > 0; i--)
   movw r24,r18
+
  4: sbiw r24, 0x00
  ret
+
   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>
 +
 
 +
das Ganze aus Spass auch noch als Keil-ARM7 Code
 +
 
 +
<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>
 
|
 
|
Zeile 202: 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 211: 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,0
+
{
   breq .L13
+
   0: push r28
   sbiw r24,1
+
   2: push r29
   rcall sum_n_rekursiv
+
   4: movw r28, r24
   add r24,r28
+
   if (n == 0)
  adc r25,r29
+
  6: sbiw r24, 0x00
.L13:
+
   8: breq .+8    ; 0x12
  pop r29
+
      return 0;
  pop r28
+
 
  ret
+
   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 235: Zeile 334:
  
 
'''Quellcodes:'''
 
'''Quellcodes:'''
 
 
{{Codevergleich|avr-gcc|BASCOM
 
{{Codevergleich|avr-gcc|BASCOM
|
+
|  
 
<pre>
 
<pre>
 
unsigned int  
 
unsigned int  
Zeile 245: 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>
 
}}
 
}}
  
'''Compilat:'''
 
  
 
{{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
   clr r1
+
   2: movw r18, r0
   add r18,r24
+
   4: mul r24, r25
  adc r19,r25
+
   6: add r19, r0
  movw r24,r18
+
   8: mul r25, r24
  lsr r25
+
   a: add r19, r0
  ror r24
+
   c: eor r1, r1
  ret</pre>
+
   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 298: 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 329: 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 400: 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 463: Zeile 710:
 
|  
 
|  
 
<pre>
 
<pre>
bubble_sort:
+
00000000 <bubble_sort>:
   cpi  r24,2    ; i >= 2
+
{
   brlo .L12
+
   uint8_t i;
   movw r26,r22 ; $6 = a
+
 
   add r26,r24 ; $6 += i
+
  // Bereich schrumpft von n auf 2
   adc r27,__zero_reg__
+
   for (i = n; i >= 2; i--)
   sbiw r26,1   ; $6--
+
   0: cpi r24, 0x02
.L10: ; {  i
+
  2: brcs .+54    ; 0x3a
   ldi r20,-128 ;  max = -128
+
   4: movw r26, r22
  ldi  r19,0    ; j = 0
+
   6: add r26, r24
   cp   r19,r24 ;  j < i
+
   8: adc r27, r1
  brsh .L14
+
   a: sbiw r26, 0x01
  movw r30,r22 ;  $3 = a
+
   {
.L9: ;  { j
+
      char max = CHAR_MIN;
  ld   r18,Z+  ;  $4 = * $3++
+
   c: ldi r20, 0x80
  cp   r18,r20 ;   $4 >= max
+
      uint8_t j, j_max;
  brlt .L7     {
+
 
  mov r21,r19 ;    j_max = j
+
      // im Bereich nach MAX suchen
  mov r20,r18 ;   max = $4
+
      for (j = 0; j < i; j++)
.L7:     }
+
   e: ldi r19, 0x00
  subi r19,-1  ;  j++
+
   10: cp r19, r24
  cp   r19,r24 j < i
+
  12: brcc .+18    ; 0x26
  brlo .L9
+
  14: movw r30, r22
.L14: ; j
+
      {
   movw r30,r22 $5 = a
+
        if (a[j] >= max)
  add r30,r21 $5 += j_max
+
   16: ld r18, Z+
  adc r31,__zero_reg__
+
   18: cp r18, r20
  ld   r18,X    $7 = * $6
+
   1a: brlt .+4     ; 0x20
  st   Z,r18    * $5 = $7
+
        {
  st   X,r20    * $6 = max
+
            // MAX und Pos. merken
  subi r24,1    ;  i--
+
            j_max = j;
  sbiw r26,1    $6--
+
  1c: mov r21, r19
  cpi r24,2    i >= 2
+
            max  = a[j_max];
   brsh .L10
+
  1e: mov r20, r18
.L12: ; } i
+
  20: subi r19, 0xFF
  ret
+
  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 506: 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 512: 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.


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:

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);	
}
 
BASCOM

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

Damit das Programm auch richtig läuft muß der Stackbereich deutlich erweitert werden.

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
 
BASCOM

???

Berechnung durch Formel

Quellcodes:

avr-gcc
unsigned int 
sum_n_formel (unsigned int n)
{
   return n*(n+1) / 2;
}
 
BASCOM

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


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
 
BASCOM

???

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++;
}
 
BASCOM

$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

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

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.

*** 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

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 :)

 
BASCOM

 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

Eine Funktion aufrufen

Quellcodes:

avr-gcc
#include <avr/io.h>
#include <avr/signal.h>

extern void foo();

SIGNAL (SIG_OVERFLOW1)
{
   foo();
}
 
BASCOM



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

Und hier der Arm 7 Code.

*** 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

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 :)

 
BASCOM


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;
   }
}
 
BASCOM


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
}

Und hier das Ganze noch mal als ARM7 Code

*** 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
 
BASCOM


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.

Siehe auch


LiFePO4 Speicher Test