Forum: Compiler & IDEs switch Case läuft nicht im AVR Simulator


von Ganymed (Gast)


Lesenswert?

Hallo zusammen

Ich wollte einmal analysieren, wie GCC ein Programm aufbaut,
bei dem in Abhängigkeit von einer Variablen eine bestimmte
Funktion aufgerufen wird.
Zuerst habe ich ein Programm geschrieben, dass ein Array von Funktions-
zeigern nutzt. Hat im Simulator prima geklappt. (Optimierung auf -Os)

Dann habe ich den Arrayaufruf durch eine switch case Anweisung
ersetzt (siehe unten). Mich interessiert, wie sich jetzt der 
Maschinencode
verändert.
Nun das Problem:
Das Programm arbeitet im Simulator nicht unter –Os ! Die Zählschleife
wird sauber durchgezählt (0,1,2,3) aber es wird nur die Funktion F_2
ausgeführt.
Stelle ich die Optimierung auf –O0 läuft es. Das nützt mir aber nichts,
da ich auch den Speicherbedarf und die Laufzeit beider Versionen
vergleichen möchte.

Wie bekomme ich das Programm in –Os im Simulator zum laufen?
Woran erkennt der Compiler, dass es eigentlich ein sinnloses Programm 
ist?

Die NOP-Funktion benutze ich als Orientierung in der LSS-Datei.

#include <avr/io.h>
#define nop asm volatile ("nop")

void F_0(char);
void F_1(char);
void F_2(char);
void F_3(char);


int main(void)
{

 char i;

 while(1)
 {
   for(i=0; i<4; i++)
   {
    nop;

    switch(i)
    {
     case 0 : F_0(i); break;
     case 1 : F_1(i); break;
     case 2 : F_2(i); break;
     case 3 : F_3(i); break;
    }

   }
 }
}


void F_0 (char x0)
{
 nop;
 x0++;
 x0 = x0>>2;
 PORTA = x0;
}


void F_1 (char x1)
{
 nop;
 x1 += 5;
  PORTB = x1;
}


void F_2 (char x2)
{
 nop;
 x2++;
 x2 += x2 | 0b00001111;
 PORTC = x2;
}


void F_3 (char x3)
{
 nop;
 x3++;
 x3 &= 0xF0;
 PORTD = x3;
}

von (prx) A. K. (prx)


Lesenswert?

Ist hier völlig ok. Der Compiler inlined die Funktionen und sortiert das 
sinnlose Variablengewusel komplett aus. Bleiben die Ausgabebefehle mit 
konstanten Daten und die NOPs übrig.

Diese Runde im klassischen Spiel Compiler gegen Programmierer ging 
jedenfalls klar an den Compiler.

von Ganymed (Gast)


Lesenswert?

Danke erste einmal
Bleiben für mich zwei Fragen:


1) Warum wird bei meiner Lösung mit dem Array nichts aussortiert?
  (Programm siehe unten)

2) Woran will der Compiler die Sinnlosigkeit erkennen?
   Ich hab schon viel analysiert. Führt man das Ergebnis
   einer Berechnung auf ein Hardware-Port / Zähler u.s.w
   sortiert er i.n.R. nicht aus.

Mein Programm mit dem Array:

#include <avr/io.h>
#define nop asm volatile ("nop")

typedef void (*pFunc)(char);

void F_0(char);
void F_1(char);
void F_2(char);
void F_3(char);


pFunc Arr[4] = {F_0, F_1, F_2, F_3};

int main(void)
{

 unsigned char i;

 while(1)
 {
   for(i=0; i<4; i++)
   {
    nop;
    Arr[i](i);
   }
 }
}


void F_0 (char x0)
{
 nop;
 x0++;
 x0 = x0>>2;
 PORTC = x0;
}

void F_1 (char x1)
{
 nop;
 x1 += 5;
  PORTC = x1;
}

void F_2 (char x2)
{
 nop;
 x2++;
 x2 += x2 | 0b00001111;
 PORTC = x2;
}


void F_3 (char x3)
{
 nop;
 x3++;
 x3 &= 0xF0;
 PORTC = x3;
}

von (prx) A. K. (prx)


Lesenswert?

Ganymed schrieb:

> Woran erkennt der Compiler, dass es eigentlich ein sinnloses Programm
> ist?

Inlining der Funktionen. Wenn klein genug, wird der Code direkt 
eingebaut und nicht als Funktion aufgerufen. Damit ist er dem beim GCC 
recht weit entwickelten Optimizer zugänglich und das Ergebnis sieht man.

Abhilfe: -fno-inline-small-functions.

von (prx) A. K. (prx)


Lesenswert?

Ganymed schrieb:

> 1) Warum wird bei meiner Lösung mit dem Array nichts aussortiert?
>   (Programm siehe unten)

Weil der Compiler die Funktionen dieses Mal nicht inlinen kann.

>    Ich hab schon viel analysiert. Führt man das Ergebnis
>    einer Berechnung auf ein Hardware-Port / Zähler u.s.w
>    sortiert er i.n.R. nicht aus.

Bei steigender Komplexität einer Funktion ist irgendwann die Schwelle 
zum nicht mehr sinnvollem Inlining überschritten. Diese Kalkulation ist 
allerdings alles andere als perfekt. Zumal er diese Entscheidung 
möglicherweise vor der Optimierung trifft.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ganymed schrieb:
> 1) Warum wird bei meiner Lösung mit dem Array nichts aussortiert?
>   (Programm siehe unten)

Die Werte in Arr[] sind zur Compilezeit nicht bekannt. Es ist denkbar, 
daß sie vor der Verwendung in main in einem anderen Modul verändert 
werden.

Um die Optmierung prinzipiell zu ermöglichen müsste Arr[] read-only 
sein, also const.

Allerdings ist selbst dann einiges zu tun für einen Compiler, um das 
dann zu optimiern:
-- die i-Schleife muss aufgerollt werden
-- es muss erkannt werden, daß Arr[i] zur Compilezeit bekannte werte hat
-- Die indirekten Aufrufe Arr[i]() müssten in direkte umgewandelt
   werden
-- ggf. Arr[i] inlinen
-- weiter optimieren

GCC 4.3 ist noch nicht so weit.

Johann

von Ganymed (Gast)


Lesenswert?

>GCC 4.3 ist noch nicht so weit.

Aber schon verdammt weit! Hut ab vor den
Programmieren kann ich da nur sagen.

Das mit dem Inlinimg der Funktionen ist
mir jetzt klar und habe ich in meinem
Code auch schon beobachtet.
Warum aber, in meinem switch-case-Prog.,
die Funktion F_3 aussortiert wird und die F_2
nicht, ist mir immer noch nicht klar.
Die Funktionen haben die gleiche "Kömplexität"
und greifen auf verschiedene Ports zu.

von (prx) A. K. (prx)


Lesenswert?

F2: x2 += x2 | 0b00001111;
Das sind 2 Operationen:
  temp = x2 | const;
  x2 = x2 + temp;

F3: x3 &= 0xF0;
Und das ist nur eine Operation.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Ganymed schrieb:

> Warum aber, in meinem switch-case-Prog.,
> die Funktion F_3 aussortiert wird und die F_2
> nicht, ist mir immer noch nicht klar.
> Die Funktionen haben die gleiche "Kömplexität"
> und greifen auf verschiedene Ports zu.

Nein, F2 ist komplexer. Es hat mehr Operationen. Nach den Tree-Passes 
sehen F2/F3 so aus:
1
;; Function F_2 (F_2)
2
3
F_2 (x2)
4
{
5
  char x2.28;
6
  unsigned char D.1254;
7
8
<bb 2>:
9
  __asm__ __volatile__("nop"::);
10
  x2.28 = x2 + 1;
11
  D.1254 = (unsigned char) (x2.28 | 15) + (unsigned char) x2.28;
12
  *53B ={v} D.1254;
13
  return;
14
}
15
16
;; Function F_3 (F_3)
17
18
F_3 (x3)
19
{
20
  char x3.34;
21
  unsigned char x3.5;
22
23
<bb 2>:
24
  __asm__ __volatile__("nop"::);
25
  x3.34 = x3 + 1;
26
  x3.5 = (volatile uint8_t) (x3.34 & -16);
27
  *50B ={v} x3.5;
28
  return;
29
}

Dies führt dazu, daß F2 durch 11 Insns dargestellt wird, F3 jedoch durch 
nur 10 Insns.

Wenn du wirklich nachverfolgen willst, wie gcc an dem Code rumbastelt, 
kannst du Dumps erzeugen lassen mit.
1
   -fdump-tree-all-details 
2
   -fdump-rtl-all-details
3
   -save-temps
4
   -fverbose-asm
5
   -dP

Wenn -dP zu viel des Guten ist im Assembler (*.s), dann einfach 
weglassen oder -dp angeben stattdessen.

Die Tree-Dumps sind C-ähnlich und lassen sich problemlos verstehen. 
Falls Fragen zu RTL sind kann ich die gerne beantworten.

Johann

von Ganymed (Gast)


Lesenswert?

Danke an alle :-)
Meine Fragen sind beantwortet.

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.