-Os liefert schlechtes Ergebnis

OP #7438631
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...

#7438652
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

#7438653
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.

#7438669
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

Moderator Persönliche Seite #7438670
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.

OP #7439217
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…

#7439272
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.

#7439274
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?

#7439411
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.

OP #7439526
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

#7439539
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?

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.