Forum: Mikrocontroller und Digitale Elektronik -Os liefert schlechtes Ergebnis


von Ingo L. (corrtexx)


Lesenswert?

Hallo Forum,

ich habe folgenden Code auf einem Atmega328 @1MHz am Laufen:
1
void Multiplex ( unsigned int Zeit )
2
{
3
  static unsigned char DigitCount = 0;
4
  uint8_t Sekunden, Minuten;
5
6
  // Update MUX
7
  DigitCount++;
8
  DigitCount &= 3;
9
  
10
  // Clear all Digits
11
  PORTB &= ~( ( 1<<PORTB6 ) | ( 1<<PORTB0 ) | ( 1<<PORTB1 ) | ( 1<<PORTB2 ) );
12
  
13
  // Clear Databus
14
  PORTD = 0;
15
  
16
  
17
  if ( Zeit >= 6000 ){ // mm:ss  
18
    // Update Digits
19
    Minuten = Zeit / 6000;
20
    Sekunden = (Zeit/100) % 60;
21
    
22
    switch ( DigitCount ){
23
      case 0:{
24
        PORTD = Ziffer[Sekunden%10];
25
        PORTB |= ( 1<<PORTB6 );
26
        break;
27
      }
28
    
29
      case 1:{
30
        PORTD = Ziffer[Sekunden/10];
31
        if ((Sekunden % 10 ) % 2 )    PORTD |= SEG_DP;
32
        else              PORTD &= ~SEG_DP;
33
        PORTB |= ( 1<<PORTB0 );
34
        break;
35
      }
36
    
37
      case 2:{
38
        PORTD = Ziffer[Minuten%10];
39
        if ((Sekunden % 10 ) % 2 )    PORTD |= SEG_DP;
40
        else              PORTD &= ~SEG_DP;
41
        PORTB |= ( 1<<PORTB1 );
42
        break;
43
      }
44
    
45
      case 3:{
46
      //  PORTD = Ziffer[Minuten/10];
47
      //  PORTB |= ( 1<<PORTB2 );
48
        break;
49
      }
50
    }
51
    if (!Clock.State)  PORTD |= SEG_DP;
52
  }
53
}

Die Funktion wird mit 1kHz aufgerufen.
Dabei passiert es, dass im Falle Zeit >= 6000 im case 2 das Digit nur 
ein sehr kurzen Moment angeschaltet wird (210µs statt wie bei den 
Anderen 490µs), da offensichtlich für etwas vorauslaufendes sehr viel 
Zeit benötigt wird. Ändert man die Optimierung vo -Os auf -O1 oder -O2, 
ist die Einschaltzeit >950µs, bei allen Digits gleich.

Tauscht man bei -Os die Zeile 38 gegen 24 wandert die lange 
Bearbeitungszeit zu dem anderen Digit. Offensichtlich stört sich der 
Compiler erheblich an
1
PORTD = Ziffer[Minuten%10];

Mit -Os:
        Program Memory Usage   :  1784 bytes   5,4 % Full
        Data Memory Usage     :  49 bytes   2,4 % Full
1
        PORTD = Ziffer[Minuten%10];
2
 12a:  2a e0         ldi  r18, 0x0A  ; 10
3
 12c:  62 2f         mov  r22, r18
4
 12e:  0e 94 36 03   call  0x66c  ; 0x66c <__udivmodqi4>
5
 132:  e9 2f         mov  r30, r25
6
 134:  f0 e0         ldi  r31, 0x00  ; 0
7
 136:  ef 5f         subi  r30, 0xFF  ; 255
8
 138:  fe 4f         sbci  r31, 0xFE  ; 254
9
 13a:  80 81         ld  r24, Z
10
 13c:  8b b9         out  0x0b, r24  ; 11

Mit -O1:
        Program Memory Usage   :  1992 bytes   6,1 % Full
        Data Memory Usage     :  41 bytes   2,0 % Full
1
          PORTD = Ziffer[Minuten%10];
2
 2aa:  8d ec         ldi  r24, 0xCD  ; 205
3
 2ac:  e8 9f         mul  r30, r24
4
 2ae:  81 2d         mov  r24, r1
5
 2b0:  11 24         eor  r1, r1
6
 2b2:  86 95         lsr  r24
7
 2b4:  86 95         lsr  r24
8
 2b6:  86 95         lsr  r24
9
 2b8:  98 2f         mov  r25, r24
10
 2ba:  99 0f         add  r25, r25
11
 2bc:  99 0f         add  r25, r25
12
 2be:  89 0f         add  r24, r25
13
 2c0:  88 0f         add  r24, r24
14
 2c2:  9e 2f         mov  r25, r30
15
 2c4:  98 1b         sub  r25, r24
16
 2c6:  89 2f         mov  r24, r25
17
 2c8:  e9 2f         mov  r30, r25
18
 2ca:  f0 e0         ldi  r31, 0x00  ; 0
19
 2cc:  ef 5f         subi  r30, 0xFF  ; 255
20
 2ce:  fe 4f         sbci  r31, 0xFE  ; 254
21
 2d0:  90 81         ld  r25, Z
22
 2d2:  9b b9         out  0x0b, r25  ; 11

Der hier speichersparende Aufruf von __udivmodqi4> führt offensichrlich 
zu einer erheblichen Verlangsamung gegenüber der direkten Berechnung 
ohne Funktionsaufruf. Das Verhalten zeigte sich mit dem AVR GCC 7.4. und 
9.2 identisch. Dieses Beispiel soll zeigen, dass die Optimierung 
durchaus nennenswert dazwischen grätschen kann. Evtl. hilft das ja 
irgendwem irgendwann einmal weiter ;)

P.S.:
Die Abfrage
1
if ( Zeit >= 6000 ){ // mm:ss
ist notwendig, weil es noch andere Behandlungen von Zeit gibt, bei der 
die Problematik jedoch nicht auftaucht, nur falls jemand fragt...

: Gesperrt durch Moderator
von Peter D. (peda)


Lesenswert?

Ingo L. schrieb:
> Die Funktion wird mit 1kHz aufgerufen.

Da kein Mensch 1000 Werte je Sekunde ablesen kann, nimmt man einfach die 
ganzen Berechnungen aus dem kritischen Teil raus und führt sie nur 
einmalig je neuem Wert aus. Ergonomisch ist eine Anzeigerate von 2..5Hz.

Besonders die Divisionen sind teuer, da der AVR keinen Befehl dafür hat.

von Harald K. (kirnbichler)


Lesenswert?

-Os bedeutet "optimize size". Vielleicht ist das nicht die beste Option 
für Deinen Anwendungsfall.

http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

von Wilhelm M. (wimalopaan)


Lesenswert?

Der 328 kann 20MHz.

Statt die Zeit in einem Zähler zu verwalten,nimm gleich die zerbrochene 
Darstellung in sec, min,Std,... . Dann vermeidest du divmod .

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Ingo L. schrieb:
> Dieses Beispiel soll zeigen, dass die Optimierung
> durchaus nennenswert dazwischen grätschen kann.

Was heisst dazwischengraetschen? Du sagst dem Compiler, er soll auf 
Groesse optimieren und dann moserst du rum, dass er nicht auf 
Geschwindigkeit optimiert?
Da kann der Compiler nix dazu, finde ich. Der macht genau das, was du 
ihm sagst. Und nicht das, was du vielleicht meinst.

Gruss
WK

von Wastl (hartundweichware)


Lesenswert?

Ingo L. schrieb:
> Der hier speichersparende Aufruf von __udivmodqi4> führt offensichrlich
> zu einer erheblichen Verlangsamung gegenüber der direkten Berechnung
> ohne Funktionsaufruf.

Da trägst du Eulen nach Athen. Ist eigentlich bekannt.

Funktionsaufrufe kosten Zeit da Register gesichert und restauriert
werden müssen, und die Parameter Übergabe braucht auch ein paar
Takte.

Das alles bei einem 8-Bitter ist eben ganz schön aufwendig.

von Peter D. (peda)


Lesenswert?

Ingo L. schrieb:
> Dieses Beispiel soll zeigen, dass die Optimierung
> durchaus nennenswert dazwischen grätschen kann.

Die mit Abstand beste Optimierung macht immer noch Brain 1.0.
Einfach mal überlegen, welche Abläufe man unnötig oft ausführt.

von Joachim B. (jar)


Lesenswert?

Ingo L. schrieb:
> if ( Zeit >= 6000 ){ // mm:ss
>     // Update Digits
>     Minuten = Zeit / 6000;
>     Sekunden = (Zeit/100) % 60;

wenn es so zeitkritsch wird würde ich zu oft auf Teilen und Modulo 
verzichten

Ingo L. schrieb:
> Die Funktion wird mit 1kHz aufgerufen.

1000x pro Sekunde ist Unfug, 5x bis 10x zu STRING wäre mehr als 
ausreichend und STRING Vergleiche "HH:MM:SS"geht viel schneller

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Da kein Mensch 1000 Werte je Sekunde ablesen kann, nimmt man einfach die
> ganzen Berechnungen aus dem kritischen Teil raus und führt sie nur
> einmalig je neuem Wert aus.

Genau. Allein dass hier:

> if ( Zeit >= 6000 ){ // mm:ss
>     Minuten = Zeit / 6000;
>     Sekunden = (Zeit/100) % 60;

wird 1000 mal pro Sekunde gemacht, obwohl sich die Werte nur jede 
Sekunde ändern - für die Minuten sogar nur alle 60 Sekunden.

Die Division und Modulo-Rechnung mit 10 macht das Ganze nicht beser:

> PORTD = Ziffer[Sekunden/10];
>         if ((Sekunden % 10 ) % 2 )    PORTD |= SEG_DP;

Hier empfiehlt sich, einfach folgende Variablen zu nutzen:

- Minuten_1 und Minuten_10 für Einer- und Zehnerstelle
- Sekunden_1 und Sekunden_10 für Einer- und Zehnerstelle

Diese 4 Variablen inkrementiert man zu geeigneter Zeit (bzw. setzt sie 
zurück), dann entfallen sowohl die Divisionen mit 6000 und 10 und die 
Modulo-Operationen mit 60 und 10 ebenso.

von Peter D. (peda)


Lesenswert?

Ingo L. schrieb:
> Der hier speichersparende Aufruf von __udivmodqi4> führt offensichrlich
> zu einer erheblichen Verlangsamung gegenüber der direkten Berechnung
> ohne Funktionsaufruf.

Du vergleichst Äpfel mit Birnen. Einmal wird eine Divisionsroutine /x 
aufgerufen. Das andere Mal wird ein optimierter Code für /10 eingefügt.

von Ingo L. (corrtexx)


Lesenswert?

Es ging nicht darum:
- wie gehts besser
- wie macht man es richtig
sondern:
- welchen Einfluss hat der Optimizer bei ggf. Ungünstiger Programmierung

Hier in dem Beispiel, hat er signifikanten Einfluss. Dass man das 
Problem hätte umschiffen können, ganz klar. Aber bei einer Stoppuhr, die 
im 10ms Bereich auflöst und weiter nichts macht, habe ich die wenigen 
Zeilen direkt in die ISR gepackt. Hätte, wenn und aber…

von Wilhelm M. (wimalopaan)


Lesenswert?

Ingo L. schrieb:
> Es ging nicht darum:
> - wie gehts besser
> - wie macht man es richtig
> sondern:
> - welchen Einfluss hat der Optimizer bei ggf. Ungünstiger Programmierung

Wie Dein Nickname schon sagt, scheinst Du so gar keine Ahnung von den 
kleinen µC zu haben. Operationen wie / oder %, die nicht durch ein shift 
und/oder Maskierung realisiert werden können, müssen dabei immer(!) ein 
Alarmzeichen sein.

von Harald K. (kirnbichler)


Lesenswert?

Nun, dazu kommt die Erwartungshaltung, die man an den Optimizer hat. 
Wenn Du eine Optimierung auf Geschwindigkeit erwartest, aber eine 
Optimierung auf Codegröße veranlasst, dann wird Dich das Ergebnis halt 
... überraschen.

Hast Du denn Deine Untersuchung mit einer anderen Einstellung für den 
Optimizer wiederholt?

von Ingo L. (corrtexx)


Lesenswert?

Harald K. schrieb:
> Hast Du denn Deine Untersuchung mit einer anderen Einstellung für den
> Optimizer wiederholt?
Ja, jede andere Einstellung, sogar -Oo, funktioniert besser als -Os

von Rolf M. (rmagnus)


Lesenswert?

Es heißt zwar oft, dass man keine Mikrooptimierungen betreiben und das 
lieber dem Compiler überlassen soll, aber das bedeutet nicht, dass man 
seinen Code schreiben kann wie man will, und der Optimizer richtet das 
dann schon irgendwie. Gerade auf so kleinen µCs muss man da etwas 
umsichtiger agieren.

Ingo L. schrieb:
> Harald K. schrieb:
>> Hast Du denn Deine Untersuchung mit einer anderen Einstellung für den
>> Optimizer wiederholt?
> Ja, jede andere Einstellung, sogar -Oo, funktioniert besser als -Os

Das erstaunt mich, denn so eine Einstellung gibt es nicht.
Was verstehst du unter "besser als -Os"? Wie schon mehrfach gesagt 
wurde: Das s in -Os steht für size, also wird darauf optimiert, den 
Code kleiner zu machen, auch wenn er dadurch langsamer werden sollte. 
Exakt das passiert bei dir.

von Ingo L. (corrtexx)


Lesenswert?

Rolf M. schrieb:
> Das erstaunt mich, denn so eine Einstellung gibt es nicht.
-Oo => ohne Optimierung

> Was verstehst du unter "besser als -Os"?
Das Programm ist deutlich schneller

> Wie schon mehrfach gesagt
> wurde: Das s in -Os steht für size, also wird darauf optimiert, den
> Code kleiner zu machen, auch wenn er dadurch langsamer werden sollte.
> Exakt das passiert bei dir.
Ja, dass habe ich nie abgestritten

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Ingo L. schrieb:
> Ja, dass habe ich nie abgestritten

Aber wozu dann hier das Ganze?
Ich mach' doch auch nicht einen Thread in einem Fastfoodforum auf, weil 
ich, immer wenn ich am Imbiss Pommes bestelle, dann keine Currywurst 
bekomme, sondern - oh Wunder - Pommes.

Gruss
WK

von Rolf M. (rmagnus)


Lesenswert?

Ingo L. schrieb:
> Rolf M. schrieb:
>> Das erstaunt mich, denn so eine Einstellung gibt es nicht.
> -Oo => ohne Optimierung

Nein, -Oo => Fehlermeldung
Ohne Optimierung ist -O0 (Null, kein o).

>> Was verstehst du unter "besser als -Os"?
> Das Programm ist deutlich schneller

Da es nicht die primäre Aufgabe von -Os ist, das Programm schneller zu 
machen, würde "besser als -Os" nicht "schneller", sondern "kleiner" 
bedeuten.

>> Wie schon mehrfach gesagt
>> wurde: Das s in -Os steht für size, also wird darauf optimiert, den
>> Code kleiner zu machen, auch wenn er dadurch langsamer werden sollte.
>> Exakt das passiert bei dir.
> Ja, dass habe ich nie abgestritten

Warum zeigst du dich dann so überrascht darüber, dass -Os das tut, was 
es soll?

von (prx) A. K. (prx)


Lesenswert?

Im GCC kann man Optimierung auch im Quellcode sektionsweise steuern.

von Rainer W. (rawi)


Lesenswert?

Ingo L. schrieb:
>> Was verstehst du unter "besser als -Os"?
> Das Programm ist deutlich schneller

"Besser" oder "schlechter" kennt der Compiler nicht.
Wenn du "schneller" möchtest, musst du ihm das sagen.

von Ingo L. (corrtexx)


Lesenswert?

@Mod
Lösch den Threat einfach löschen bitte...

Beitrag #7439590 wurde von einem Moderator gelöscht.
Beitrag #7439776 wurde von einem Moderator gelöscht.
Beitrag #7439813 wurde von einem Moderator gelöscht.
Beitrag #7439817 wurde von einem Moderator gelöscht.
Dieser Beitrag ist gesperrt und kann nicht beantwortet werden.