Forum: Compiler & IDEs Verstehe ARM Code nicht


von Martin O. (ossi-2)


Lesenswert?

Zur Zeit arbeite ich mich in das Teensy 36 Board ein. Da es die erste 
ARM CPU ist, die ich benutze, interessiert mich natürlich die 
Lesitungsfähigkeit, weshalb ich kleine Programme schreibe und deren 
Laufzeit messe. Um die Laufzeit zu verstehen will ich mir auch den vom 
Compiler erzeugten Code ansehen. Das gelingt auch schon ganz gut, aber 
bei dem unten gezeigten Beispiel habe ich eine Frage.

Auf Adresse 4ae wird, soweit ich durchblicke , ein unbedingter Sprung 
erzeugt, der beim ersten Durchlauf der Schleife die Cosinusauswertung 
überspringt. Das kann aber nicht sein, denn die Probe zeigt, dass der 
Code auch für einen einzigen Schleifendurchlauf das richtige Resultat 
erzeugt.
Die Frage ist also:

Was bewirkt der Befehl auf Adresse 4ae?
1
void setup() {         
2
  Serial.begin(9600);  
3
  delay(2000) ;
4
  float result=time1(9999) ;
5
  Serial.printf(" result1=%10.5f\n",result) ;
6
  result=time1(random(100)) ;
7
  Serial.printf(" result2=%10.5f\n",result) ;
8
  }
9
10
float time1(int d){ 
11
  float fd1,fsum ;
12
  int32_t Nloop ;
13
  
14
  Nloop=10000-d ;
15
  fd1=1.5 ;
16
  fsum=0.0 ;
17
  for(int k=0 ; k<Nloop ; k++){
18
    fd1 += 0.5 ;
19
    fsum += cosf(fd1) ;
20
    }
21
  return fsum ;  
22
  }
23
24
void loop() {
25
  }
26
*****************************************************************************************
27
  float time1(int d){ 
28
  float fd1,fsum ;
29
  int32_t Nloop ;
30
  
31
  Nloop=10000-d ;
32
     492:  f5c0 551c   rsb r5, r0, #9984 ; 0x2700
33
     496: 3510        adds  r5, #16
34
  fd1=1.5 ;
35
  fsum=0.0 ;
36
  for(int k=0 ; k<Nloop ; k++){
37
     498: 2d00        cmp r5, #0
38
     49a: dd22        ble.n 4e2 <setup+0x72>
39
     49c: ed9f 0a14   vldr  s0, [pc, #80] ; 4f0 <setup+0x80>
40
     4a0: eddf 8a14   vldr  s17, [pc, #80]  ; 4f4 <setup+0x84>
41
     4a4: 2400        movs  r4, #0
42
     4a6: eeb7 8a08   vmov.f32  s16, #120 ; 0x3fc00000  1.5
43
    fd1 += 0.5 ;
44
     4aa: eeb6 9a00   vmov.f32  s18, #96  ; 0x3f000000  0.5
45
     4ae: e003        b.n 4b8 <setup+0x48>           ???????????????????????????????????????????
46
     4b0: ee38 0a09   vadd.f32  s0, s16, s18
47
     4b4: f002 f89a   bl  25ec <cosf>                Aufruf cosinus
48
  int32_t Nloop ;
49
  
50
  Nloop=10000-d ;
51
  fd1=1.5 ;
52
  fsum=0.0 ;
53
  for(int k=0 ; k<Nloop ; k++){
54
     4b8: 3401        adds  r4, #1                   Schleifenzähler
55
     4ba: 42a5        cmp r5, r4                     Bedingung
56
    fd1 += 0.5 ;
57
     4bc: ee38 8a09   vadd.f32  s16, s16, s18        fd1 += 0.5
58
    fsum += cosf(fd1) ;
59
     4c0: ee78 8a80   vadd.f32  s17, s17, s0         fsum += cos(..)
60
  int32_t Nloop ;
61
  
62
  Nloop=10000-d ;
63
  fd1=1.5 ;
64
  fsum=0.0 ;
65
  for(int k=0 ; k<Nloop ; k++){
66
     4c4: d1f4        bne.n 4b0 <setup+0x40>           Schleifensteuerung
67
     4c6: ee18 0a90   vmov  r0, s17
68
     4ca: f003 f82d   bl  3528 <__aeabi_f2d>
69
     4ce: 4602        mov r2, r0
70
     4d0: 460b        mov r3, r1
71
*****************************************************************************************

von (Gast)


Lesenswert?

Martin O. schrieb:
> Auf Adresse 4ae wird, soweit ich durchblicke , ein unbedingter Sprung
> erzeugt, der beim ersten Durchlauf der Schleife die Cosinusauswertung
> überspringt.

Das täuscht, die Cosinus-Berechnung wird nicht übersprungen. Zuerst muss 
ja die Schleifenbedinung ausgewertet werden. Der unbedingte Sprung in 
4ae geht zur Schleifenbedingung. Wenn die != 0 ergibt springt bne.n (in 
4c4) zur Addition in 4b0 und in Folge zum Cosinus.

von Martin O. (ossi-2)


Lesenswert?

Aber der Sprung geht ja nur bis 4b8. Die Befehle auf 4bc und 4c0 würden 
auf jeden Fall einmal ausgeführt, was aber nicht korrekt wäre.

von Dr. Sommer (Gast)


Lesenswert?

Martin O. schrieb:
> Die Befehle auf 4bc und 4c0 würden auf jeden Fall einmal ausgeführt, was
> aber nicht korrekt wäre.

Nö, wenn d=10000 wird bei 49a direkt ans Ende gesprungen. Der Compiler 
hat den 1. Schleifendurchlauf wegoptimiert und durch Konstanten ersetzt.

PS: Findet ihr es nicht auch furchtbar unübersichtlich wenn der 
originale C Code mit in das Disassembly gemischt wird? Vor allem bei 
eingeschalteter Optimierung...

von Martin O. (ossi-2)


Lesenswert?

@Dr. Sommer: Deine Erklärung macht Sinn, besten Dank. Wenn man ein 
leicht geändertes Programm nimmt, kann der Compiler keinen 
Schleifenduchauf mehr wegoptimieren, dann wird der erzeugte Code etwas 
besser verständlich.

Ich finde diesen Code-misch-masch auch schwer lesbar. Insbesondere sind 
viele Konstanten für mich nicht verständlich. Zumal der Compiler 
manchmal ganze Schleifen wegoptimiert.

von Dr. Sommer (Gast)


Lesenswert?

Martin O. schrieb:
> Ich finde diesen Code-misch-masch auch schwer lesbar.

Wenn du die "-S" Option bei "objdump" raus nimmst wird der Quellcode 
beim Disassemblieren weggelassen.

von x^y (Gast)


Lesenswert?

Deine Konstanten implizieren "double". Mit Optimierung war dein Compiler 
so nett manche als "float" zu kodieren. Das muss er aber nicht tun - und 
wird das bei einigen auch nicht, falls keine 1:1 Abbildung des Wertes 
zwischen double und float existiert. Dann führt er ein double cast aus. 
Such mal nach Stellen wie diesen wenn du schonmal das Disassembly hast:
1
4ca: f003 f82d   bl  3528 <__aeabi_f2d>

von Dr. Sommer (Gast)


Lesenswert?

Die Variablen sind aber vom Typ float. Der Compiler wird wohl kaum ein 
double-Literal laden und zur Laufzeit nach float konvertieren; das macht 
er schon vorher beim Kompilieren. Höchstens das "fd1 += 0.5;" könnte mit 
double-Präzision ausgeführt werden, wobei der Compiler hier erkennen 
müsste dass das nichts bringt.

von x^y (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Die Variablen sind aber vom Typ float.

Eben.

> Der Compiler wird wohl kaum ein
> double-Literal laden und zur Laufzeit nach float konvertieren;

Nein, er wird ein double Literal laden und die float Variable zur 
Laufzeit auf double konvertieren, dann die Operation (in double) 
ausführen und zur Zuweisung wieder zurück in float konvertieren. Ist ein 
Operand double erfolgt nach Regelwerk eine Konvertierung des zweiten 
Operands ebenfalls auf double und kein impliziter Rückcast zu float.

> das macht
> er schon vorher beim Kompilieren. Höchstens das "fd1 += 0.5;" könnte mit
> double-Präzision ausgeführt werden, wobei der Compiler hier erkennen
> müsste dass das nichts bringt.

Null und 0.5 sind exakt abbildbar, der Optimizer checkt das meist. 
Einfach was probieren was nicht 1:1 abgebildet ist, 1.731 oder sowas. 
Oder einfach mal mit -O0 compilieren, dann macht der Compiler Dienst 
nach Vorschrift.

von Dr. Sommer (Gast)


Lesenswert?

x^y schrieb:
> Nein, er wird ein double Literal laden und die float Variable zur
> Laufzeit auf double konvertieren,

Aber nicht bei sowas:
1
  fd1=1.5 ;
2
  fsum=0.0 ;

von x^y (Gast)


Lesenswert?

Man müßte höchstens noch aufpassen, ob man den Compiler die Annahme z.B. 
von Rounding Mode und ähnlichen Spezifika verbieten kann. Dann müßte die 
Konvertierung einer double Konstanten zu float theoretisch per 
Instruktion passieren.

Ich war z.B. überrascht, dass dies hier:
1
float x = 0.0f/0.0f; /* NaN */

vom Compiler je nach Einstellung tatsächlich als Division durch Null 
ausgeführt werden kann. Schnell getestet:
1
   0:  2100        movs  r1, #0
2
   2:  e92d 41f0   stmdb  sp!, {r4, r5, r6, r7, r8, lr}
3
   6:  4608        mov  r0, r1
4
   8:  f7ff fffe   bl  0 <__aeabi_fdiv>
5
...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.