Forum: Compiler & IDEs avrgcc erzeugt sinnlosen code?


von Sebastian S. (sebion7125)


Lesenswert?

Hallo,

Ich habe heute in den vom avrgcc generierten assemblercode mal 
reingelinst und glaube da sinnlosen code zu sehen. vermutlich überseh 
ich ja nur etwas aber würde mich mal interessieren ob mir jemand meinen 
denkfehler erklären kann oder ob ich tatsächlich über einen compilerbug 
gestolpert sein sollte:
1
UCHAR TransformByLookUp(long In)
2
{  
3
     62e:  9b 01         movw  r18, r22
4
     630:  ac 01         movw  r20, r24
5
  UCHAR Result, cTemp;
6
  uint16_t In_h = (uint16_t)(In>>16);
7
     632:  ca 01         movw  r24, r20
8
     634:  bb 27         eor  r27, r27
9
     636:  97 fd         sbrc  r25, 7
10
     638:  b0 95         com  r27
11
     63a:  ab 2f         mov  r26, r27
12
     63c:  dc 01         movw  r26, r24
13
...

Mir geht es speziell um die letzten beiden assemblerbefehle. Wieso 
kopiert er von r27 nach r26 daten um dann im nächsten schritt den inhalt 
von r24-25 nach r26-27 zu kopieren? die zeile könnte man doch 
weglassen?!?

Ich verwende gcc 4.3.2 in winavr 20090313. Ich wollte schonmal auf den 
neueren aus avrstudio 5 umsteigen, jedoch hat dieser bedeutend (ca. um 
10%) größeren code generiert (Falls jemand mir sagen kann wie ich am 
schnellsten herausfinden kann was da am code größer geworden ist wäre 
ich natürlich auch sehr dankbar. Das Programm ist leider so komplex 
geworden, dass ich ungern die ganzen beiden lss dateien lesen und 
vergleichen will ;) )

: Verschoben durch Moderator
von Peter II (Gast)


Lesenswert?

optimierung auch gleich eingeschaltet?

von Sebastian S. (sebion7125)


Lesenswert?

compiler einstellungen sind in beiden fällen natürlich auf -Os gestellt 
;)

von Εrnst B. (ernst)


Lesenswert?

Sebastian Steppeler schrieb:
> oder ob ich tatsächlich über einen compilerbug
> gestolpert sein sollte

Naja, "bug" würde ich das nicht nennen, eher "ausgelassene 
Optimierungsmöglichkeit".

Der gcc hat ja nicht den Anspruch, den 
"kleinstmöglichsten/schnellstmöglichen" Binärcode zu erzeugen, sondern 
erstmal die Aufgabe korrekt zu Compilieren.

Oder: mit handoptimiertem ASM kann man fast immer noch ein paar 
Taktzyklen rausholen.

Nachdem deine GCC-Version von 2009 ist: Hast du mal eine aktuelle 
dagegengetestet?

von Sebastian S. (sebion7125)


Lesenswert?

Naja ist schon klar, dass die optimierung nicht perfekt ist, aber dass 
er so offensichtlich sinnlosen code erzeugt ist mir schleierhaft, ich 
würde nichtmal erwarten dass er mir sone zeile produziert wenn die 
optimierung komplett aus wäre, aber compilerbau ist eine wissenschaft 
für sich und ich weiß nicht ob ich das so einfach beurteilen kann.

Habe gerade mal den gcc4.5.1 gegengetestet, der macht diesen fehler 
nicht, aber wie gesagt der code ist 10 % größer, das kann ich mir leider 
nicht leisten. Werde mal die aktuellste version runterladen und 
spaßeshalber testen.

von Sebastian S. (sebion7125)


Lesenswert?

noch eine frage: gibt es unter windows schon neuere avr-gccs als den 
4.3.3 ?
das ist der aktuelle aus dem neusten winavr.
In Avrstudio 5 ist 4.5.1 integriert, den habe ich schon getestet. Der 
macht den fehler nicht aber viel zu großen code...

von Volkmar D. (volkmar)


Lesenswert?

Sebastian Steppeler schrieb:
> noch eine frage: gibt es unter windows schon neuere avr-gccs als den
> 4.3.3 ?

Schau zum Beispiel mal hier: 
Beitrag "Re: Ist WinAVR tot ?"

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Meine Kristallkugel sagt, daß In 32-Bit signed ist. Das letzte MOV 
gehört zu dem signed-Shift.

Vielleicht hülft -f[no-]split-wide-types

Um wirklich Aussageb machen zu können, bitte compilierbaren Code 
posten, nicht ein Märlchencode wo man Typen und Code-Kontext aus dem 
Kaffeesatz lesen muss.

von Cyblord -. (cyblord)


Lesenswert?

Εrnst B✶ schrieb:
> Der gcc hat ja nicht den Anspruch, den
> "kleinstmöglichsten/schnellstmöglichen" Binärcode zu erzeugen, sondern
> erstmal die Aufgabe korrekt zu Compilieren.
Hmm also -Os teilt dem Compiler doch genau diesen Wunsch mit. So klein 
wie möglich.

> Oder: mit handoptimiertem ASM kann man fast immer noch ein paar
> Taktzyklen rausholen.

Da gehe ich nicht ganz d'accord mit dir ;-) Ob das "fast immer" so 
stimmt glaube ich nicht. Ausserdem muss man sich fragen zu welchem 
Preis? Die Wahrscheinlichkeit ist groß das am Ende die ganze Funktion 
nicht mehr (immer) genau das tut was sie soll. Oftmals stellen sich doch 
auf den ersten Blick unnötige ASM Befehle bei genauerem hinsehen als 
unverzichtbar heraus.

Gerade bei diesem Beispiel, warum sollte der Compiler völlig unnötige 
Befehle an den Schluss hängen? Ich meine dass er mal ungeschickt 
optimiert kann vorkommen, aber sowas.

Und ja, hier würde mich die Funktion in C Quellcode auch interessieren. 
Ohne kann man da sowieso nicht viel sagen.

gruß cyblord


PS: Müsste dieser Thread nicht in die gcc Rubrik?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

cyblord ---- schrieb:
> Hmm also -Os teilt dem Compiler doch genau diesen Wunsch mit. So klein
> wie möglich.

Ja, und?  Woher soll er das wissen?

Das ist doch kein Mensch, sondern der arbeitet formale Algorithmen
ab.  Wenn da eine bestimmte Optimierungsmöglichkeit noch nicht
erfasst worden ist, kann der Compiler sie auch nicht passend
generieren.

-Os wird gern überschätzt.  Dabei ist die Option so dokumentiert:
"Benutze all die Optimierungen, die über -O1 hinausgehen, und von denen
bekannt bzw. zu erwarten ist, dass sie nicht zu einer Vergrößerung
des generierten Codes führen."

> PS: Müsste dieser Thread nicht in die gcc Rubrik?

Wäre sinnvoll, ich schieb' ihn mal.

von Sebastian S. (sebion7125)


Lesenswert?

Jörg Wunsch schrieb:

>> PS: Müsste dieser Thread nicht in die gcc Rubrik?
>
> Wäre sinnvoll, ich schieb' ihn mal.

Ich hatte ihn hier gepostet, weil es ja speziell um den avr-gcc ging, 
aber ist ja dann ganz gut so.

Johann L. schrieb:
> Meine Kristallkugel sagt, daß In 32-Bit signed ist. Das letzte MOV
> gehört zu dem signed-Shift.
>
> Vielleicht hülft -f[no-]split-wide-types

Das kann man an dem codeschnipsel ja auch erkennen(siehe zeile 1).
Was genau ist ein signed-Shift?

hier mal der komplette code der funktion:
1
// lookup table:
2
long lLookUpl[63];
3
uint16_t iLookUph[64];
4
5
UCHAR TransformByLookUp(long In)
6
{  
7
  UCHAR Result, cTemp;
8
  uint16_t In_h = (uint16_t)(In>>16);
9
  /*if(In<0)
10
    return 0;*/
11
12
  //Result = 0;
13
  
14
  // << + loop slows down conversion so lets write it out
15
  /*for(cBit=6;cBit>(signed char)0;cBit--)
16
  {
17
    cTemp = Result + (1<<cBit);
18
    if(lLookUp[(int)cTemp]<=In)
19
      Result = cTemp;
20
  }*/
21
22
  if(iLookUph[0]<=In_h)
23
  {
24
    Result = 64;
25
    
26
    cTemp = Result + 32;
27
    if(iLookUph[(int)(cTemp-64)]<=In_h)
28
      Result = cTemp;
29
30
    cTemp = Result + 16;
31
    if(iLookUph[(int)(cTemp-64)]<=In_h)
32
      Result = cTemp;
33
34
    cTemp = Result + 8;
35
    if(iLookUph[(int)(cTemp-64)]<=In_h)
36
      Result = cTemp;
37
38
    cTemp = Result + 4;
39
    if(iLookUph[(int)(cTemp-64)]<=In_h)
40
      Result = cTemp;
41
42
    cTemp = Result + 2;
43
    if(iLookUph[(int)(cTemp-64)]<=In_h)
44
      Result = cTemp;
45
        
46
    cTemp = Result + 1;
47
    if(iLookUph[(int)(cTemp-64)]<=In_h)
48
      return cTemp;
49
  }    
50
  else
51
  {
52
    Result = 0;
53
54
    cTemp = Result + 32;
55
    if(lLookUpl[(int)(cTemp-1)]<=In)
56
      Result = cTemp;
57
58
    cTemp = Result + 16;
59
    if(lLookUpl[(int)(cTemp-1)]<=In)
60
      Result = cTemp;
61
62
    cTemp = Result + 8;
63
    if(lLookUpl[(int)(cTemp-1)]<=In)
64
      Result = cTemp;
65
66
    cTemp = Result + 4;
67
    if(lLookUpl[(int)(cTemp-1)]<=In)
68
      Result = cTemp;
69
70
    cTemp = Result + 2;
71
    if(lLookUpl[(int)(cTemp-1)]<=In)
72
      Result = cTemp;
73
    
74
    // ensure 1-127 range:
75
    if(Result == 0)
76
      return 1;
77
    cTemp = Result + 1;
78
    if(lLookUpl[(int)(cTemp-1)]<=In)
79
      return cTemp;
80
  }  
81
  
82
  return Result;  
83
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> hier mal der komplette code der funktion:

code.c:1:1: error: unknown type name 'UCHAR'
code.c: In function 'TransformByLookUp':
code.c:3:3: error: unknown type name 'UCHAR'
code.c:4:3: error: unknown type name 'uint16_t'
code.c:4:20: error: 'uint16_t' undeclared (first use in this function)
code.c:4:20: note: each undeclared identifier is reported only once for 
each function it appears in
code.c:18:6: error: 'iLookUph' undeclared (first use in this function)
code.c:51:8: error: 'lLookUpl' undeclared (first use in this function)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:
> Das kann man an dem codeschnipsel ja auch erkennen(siehe zeile 1).
> Was genau ist ein signed-Shift?

Signed Variablen werden anders geschoben als unsigned-Variablen, da ja 
im ersten Fall das Vorzeichen erhalten bleiben muss. Beim Schieben nach 
rechts muss bei einem negativen Wert das oberste Bit weiterhin gesetzt 
bleiben, sonst verliert die Variable ja ihr Vorzeichen! Genau das 
berücksichtigt der gcc, wo Du denkst, das wäre hyperfluid.

Daher: Ändere die Variable "In" in "unsigned long" statt "long". 
Schiebereien von vorzeichenbehafteten Variablen machen nur in den 
wenigsten Fällen Sinn.

von Cyblord -. (cyblord)


Lesenswert?

Warum benutzt du UCHAR als Datentyp? Warum nicht char oder uint8_t 
stattdessen? Gibts da einen Vorteil?

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

cyblord ---- schrieb:
> Warum benutzt du UCHAR als Datentyp? Warum nicht char oder uint8_t
> stattdessen? Gibts da einen Vorteil?

Hat wohl MS irgendwann eingeführt, entweder kannten die da noch nicht 
stdint.h oder es hat historische Gründe, stammt also noch aus der 
Urzeit, wo es stdint.h noch nicht gab.

Siehe auch:

  http://msdn.microsoft.com/en-us/library/cc230382%28v=prot.10%29.aspx

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Um das Beispiel übersetzen zu können fehlen noch folgende typedefs, 
deklarationen, defines, was-auch-immer:

UCHAR
uint16_t
iLookUph
lLookUpl

uint16_t gibt's zum Beipiel mit #include <stdint.h>

von Sebastian S. (sebion7125)


Lesenswert?

sorry hab da wohl dann doch noch was vergessen mitzuliefern ;) ist immer 
blöd eine funktion aus einem projekt zu posten. UCHAR ist eine dumme 
alte microsoft angewohnheit von mir, die ich nutze weil sie schneller zu 
tippen geht als uint8_t ;) ich editier mal den code so um, dass er 
compilierbar sein sollte.

die bei den lookuparrays hatte ich schon mit einem edit nachgeliefert 
ohne ihn zu bezeichnen, wer konnte ahnen, dass ihr so schnell seid ;)
1
#include <avr/io.h>
2
#include <math.h>
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include <util/delay.h>
6
#include <avr/wdt.h>
7
#include <avr/interrupt.h>
8
9
10
#define UCHAR unsigned char
11
12
13
// lookup table:
14
long lLookUpl[63];
15
uint16_t iLookUph[64];
16
17
UCHAR TransformByLookUp(long In)
18
{  
19
  UCHAR Result, cTemp;
20
  uint16_t In_h = (uint16_t)(In>>16);
21
  /*if(In<0)
22
    return 0;*/
23
24
  //Result = 0;
25
  
26
  // << + loop slows down conversion so lets write it out
27
  /*for(cBit=6;cBit>(signed char)0;cBit--)
28
  {
29
    cTemp = Result + (1<<cBit);
30
    if(lLookUp[(int)cTemp]<=In)
31
      Result = cTemp;
32
  }*/
33
34
  if(iLookUph[0]<=In_h)
35
  {
36
    Result = 64;
37
    
38
    cTemp = Result + 32;
39
    if(iLookUph[(int)(cTemp-64)]<=In_h)
40
      Result = cTemp;
41
42
    cTemp = Result + 16;
43
    if(iLookUph[(int)(cTemp-64)]<=In_h)
44
      Result = cTemp;
45
46
    cTemp = Result + 8;
47
    if(iLookUph[(int)(cTemp-64)]<=In_h)
48
      Result = cTemp;
49
50
    cTemp = Result + 4;
51
    if(iLookUph[(int)(cTemp-64)]<=In_h)
52
      Result = cTemp;
53
54
    cTemp = Result + 2;
55
    if(iLookUph[(int)(cTemp-64)]<=In_h)
56
      Result = cTemp;
57
        
58
    cTemp = Result + 1;
59
    if(iLookUph[(int)(cTemp-64)]<=In_h)
60
      return cTemp;
61
  }    
62
  else
63
  {
64
    Result = 0;
65
66
    cTemp = Result + 32;
67
    if(lLookUpl[(int)(cTemp-1)]<=In)
68
      Result = cTemp;
69
70
    cTemp = Result + 16;
71
    if(lLookUpl[(int)(cTemp-1)]<=In)
72
      Result = cTemp;
73
74
    cTemp = Result + 8;
75
    if(lLookUpl[(int)(cTemp-1)]<=In)
76
      Result = cTemp;
77
78
    cTemp = Result + 4;
79
    if(lLookUpl[(int)(cTemp-1)]<=In)
80
      Result = cTemp;
81
82
    cTemp = Result + 2;
83
    if(lLookUpl[(int)(cTemp-1)]<=In)
84
      Result = cTemp;
85
    
86
    // ensure 1-127 range:
87
    if(Result == 0)
88
      return 1;
89
    cTemp = Result + 1;
90
    if(lLookUpl[(int)(cTemp-1)]<=In)
91
      return cTemp;
92
  }  
93
  
94
  return Result;  
95
}

so ich hoffe ich hab nicht wieder was vergessen.

von Sebastian S. (sebion7125)


Lesenswert?

Frank M. schrieb:
> Sebastian Steppeler schrieb:
>> Das kann man an dem codeschnipsel ja auch erkennen(siehe zeile 1).
>> Was genau ist ein signed-Shift?
>
> Signed Variablen werden anders geschoben als unsigned-Variablen, da ja
> im ersten Fall das Vorzeichen erhalten bleiben muss. Beim Schieben nach
> rechts muss bei einem negativen Wert das oberste Bit weiterhin gesetzt
> bleiben, sonst verliert die Variable ja ihr Vorzeichen! Genau das
> berücksichtigt der gcc, wo Du denkst, das wäre hyperfluid.
>
> Daher: Ändere die Variable "In" in "unsigned long" statt "long".
> Schiebereien von vorzeichenbehafteten Variablen machen nur in den
> wenigsten Fällen Sinn.

Okay dast mit dem signed shift ist mir jetzt klar, kannte ich sogar aber 
war mich nicht klar was du meintest.
Aber wo siehst du hier ein shiften? Ich sprach doch von zwei mov 
operationen von denen die zweite die erste überflüssig macht.
Btw. Der avr assembler hat eine instruktion für arithmetisches shiften, 
also macht es vom rechenaufwand keinen unterschied ob man signed oder 
unsigned shifts hat.

von Sebastian S. (sebion7125)


Lesenswert?

Volkmar Dierkes schrieb:
> Sebastian Steppeler schrieb:
>> noch eine frage: gibt es unter windows schon neuere avr-gccs als den
>> 4.3.3 ?
>
> Schau zum Beispiel mal hier:
> Beitrag "Re: Ist WinAVR tot ?"

Vielen dank für diesen tollen link. Ich habe die version 4.7.0 mal 
runtergeladen und installiert und mein projekt kompiliert. Ergebnis:
Das Kompilat ist jetzt noch kleiner als mit den 4.3.x versionen des 
compilers und die überflüssigen instruktionen sind auch nicht mehr da. 
War echt ne super hilfe :)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> Okay dast mit dem signed shift ist mir jetzt klar, kannte ich sogar aber
> war mich nicht klar was du meintest.
> Aber wo siehst du hier ein shiften? Ich sprach doch von zwei mov
> operationen von denen die zweite die erste überflüssig macht.

Neuere avr-gcc Versionen erkennen das auch, bzw. daß der high-Teil des 
geschobenen Wertes nicht benötigt wird.

Mit dem alten Compiler ist's eben so, daß erst signed geschoben wird:

In R18..R21 steht ein signed 32-Bit Wert, der um 16 nach rechts 
geschoben und dabei nach R24..R27 kopiert wird:

movw  r24, r20
eor   r27, r27
sbrc  r25, 7
com   r27
mov   r26, r27

Danach folgt dann die Extraktion des 16-Bit Wertes, was dem Cast 
entspricht.

In neueren Compilerversionen taucht dieser Shift garnicht mehr auf, er 
wird quasi implizit erledigt.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> Aber wo siehst du hier ein shiften? Ich sprach doch von zwei mov
> operationen von denen die zweite die erste überflüssig macht.

Warum hast Du es nicht einfach ausprobiert?

Hier mein Test mit WinAVR-20100110 (AVR-Studio 4 mit aktualisierter 
WinAVR-Version):
1
long In;
2
uint16_t x;
3
4
x = (uint16_t) (In>>16);
5
  ee:  80 91 24 01   lds  r24, 0x0124
6
  f2:  90 91 25 01   lds  r25, 0x0125
7
  f6:  a0 91 26 01   lds  r26, 0x0126
8
  fa:  b0 91 27 01   lds  r27, 0x0127
9
  fe:  cd 01         movw  r24, r26
10
 100:  bb 27         eor  r27, r27
11
 102:  97 fd         sbrc  r25, 7
12
 104:  b0 95         com  r27
13
 106:  ab 2f         mov  r26, r27
14
 108:  90 93 23 01   sts  0x0123, r25
15
 10c:  80 93 22 01   sts  0x0122, r24

Jetzt mit unsigned long:
1
unsigned long In;
2
uint16_t x;
3
4
x = (uint16_t) (In>>16);
5
  ee:  80 91 24 01   lds  r24, 0x0124
6
  f2:  90 91 25 01   lds  r25, 0x0125
7
  f6:  a0 91 26 01   lds  r26, 0x0126
8
  fa:  b0 91 27 01   lds  r27, 0x0127
9
  fe:  cd 01         movw  r24, r26
10
 100:  aa 27         eor  r26, r26
11
 102:  bb 27         eor  r27, r27
12
 104:  90 93 23 01   sts  0x0123, r25
13
 108:  80 93 22 01   sts  0x0122, r24

Wie dir vielleicht auffällt, sieht der Code nicht nur anders, sondern 
auch küzer aus. Auch Dein angemerktes MOV ist verschwunden.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> In neueren Compilerversionen taucht dieser Shift garnicht mehr auf, er
> wird quasi implizit erledigt.

Das ist zwar ein nettes Feature, leider verleitet es den gemeinen 
Programmierer, sich zukünftig noch dümmer anstellen zu dürfen.

Der Compiler wirds schon richten ;-)

von Sebastian S. (sebion7125)


Lesenswert?

Frank M. schrieb:
> Sebastian Steppeler schrieb:
>
>> Aber wo siehst du hier ein shiften? Ich sprach doch von zwei mov
>> operationen von denen die zweite die erste überflüssig macht.
>
> Warum hast Du es nicht einfach ausprobiert?
> ...

Naja man kann halt nicht alles wissen, und ich muss gestehen mir war 
nicht bewusst, dass die schiebeoperatoren in c sich um signed und 
unsigned scheren. Daher meine verwunderung. Aja und dann hab ich den 
shift nicht erkannt, weil ich vergas, dass ich ja um vielfache von 8 
shifte. Manchmal ist man einfach ein bisschen blind und muss auf die 
augen getreten werden ;)

Danke für die vielen umfangreichen antworten. Ich habe auf jedenfall was 
gelernt :)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:
> Naja man kann halt nicht alles wissen, und ich muss gestehen mir war
> nicht bewusst, dass die schiebeoperatoren in c sich um signed und
> unsigned scheren.

Ja, das ist so. Hier mal ein kleines Beispiel-Programm unter Linux:
1
#include <stdio.h>
2
3
int
4
main(void)
5
{
6
    int             i  = 0xFFFFFFFF;
7
    unsigned int    ui = 0xFFFFFFFF;
8
9
    i >>= 1;
10
    ui >>= 1;
11
12
    printf ("0x%08x 0x%08x\n", i, ui);
13
}

Ausgabe:

0xffffffff 0x7fffffff

Es kommt also ein komplett anderes Ergebnis heraus. Daher sollte man 
sich prinzipiell angewöhnen, bei Shifts immer (bis auf ganz wenige 
Ausnahmen) unsigned-Variablen zu verwenden. Sonst macht man sich u.U. 
das ganze Ergebnis kaputt.

> Ich habe auf jedenfall was gelernt :)

Das geht mir jeden Tag so. C ist eine Sprache, bei der man nie auslernt 
:-)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Falls es wirklich auf die Codegröße ankommt, bring eine Restrukturierung 
des Codes wesentlich mehr als ein paar optisch unhübsche MOV 
einzusparen.

Mit folgendem Code schrumpft die Größe auf rund 1/3; von ~420 auf ~150
1
#include <stdint.h>
2
3
extern uint16_t iLookUph[];
4
extern long lLookUpl[];
5
6
uint8_t TransformByLookUp (long In)
7
{  
8
  uint8_t offset;
9
  uint8_t Result, cTemp;
10
  uint16_t In_h = In >> 16;
11
12
  if (iLookUph[0] <= In_h)
13
  {
14
    Result = 64;
15
    
16
    for (offset = 32; offset != 0; offset >>= 1)
17
      {
18
        cTemp = Result + offset;
19
        if (iLookUph[cTemp - 64] <= In_h)
20
          Result = cTemp;
21
      }
22
  }    
23
  else
24
  {
25
    Result = 0;
26
27
    for (offset = 32; offset != 0; offset >>= 1)
28
      {
29
        // ensure 1-127 range:
30
        if (offset == 1 && Result == 0)
31
          return 1;
32
        cTemp = Result + offset;
33
        if (lLookUpl[cTemp - 1] <= In)
34
          Result = cTemp;
35
      }
36
  }  
37
  
38
  return Result;  
39
}

von Sebastian S. (sebion7125)


Lesenswert?

Johann L. schrieb:
> Falls es wirklich auf die Codegröße ankommt, bring eine Restrukturierung
> des Codes wesentlich mehr als ein paar optisch unhübsche MOV
> einzusparen.

Das war mir schon klar, dass der code als schleife kürzer ist. In der 
Urversion wars auch eine schleife, aber hier kommt es auf 
ausführungsgeschwindigkeit (jeder takt weniger zählt) an ;) daher hab 
ihc mir die schleifen gespart.
Ihr müsst wissen die funktion stammt aus meinem sensorsystem fürs 
klavier ( http://sebion.wordpress.com ) und da das ding 8 kanäle a 1,2 
khz sample rate bewältigt muss zumindest dieser teil des codes schnell 
gehen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> hier kommt es auf ausführungsgeschwindigkeit
> (jeder takt weniger zählt) an

Und warum wird dann auf Codegröße optimiert?

Frank M. schrieb:
> Johann L. schrieb:
>> In neueren Compilerversionen taucht dieser Shift garnicht mehr auf, er
>> wird quasi implizit erledigt.
>
> Das ist zwar ein nettes Feature, leider verleitet es den gemeinen
> Programmierer, sich zukünftig noch dümmer anstellen zu dürfen.
>
> Der Compiler wirds schon richten ;-)

Du meinst also, daß ein Compiler nicht gut optimieren sollte?
Quasi als erzieherische Maßnahme.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Du meinst also, daß ein Compiler nicht gut optimieren sollte?

Doch, schon. Aber ich finde schon, dass auch der Programmierer noch 
"mitdenken" sollte. Der Mensch verliert sonst den Blick dafür, selbst 
den Code zu optimieren - er wird schlampig.

Ich will es mal überspitzt darstellen: Heraus kommen dann zum Beispiel 
im PC-Bereich Programme, die (mittlerweile) mehrere GB an RAM verbraten, 
obwohl es bei "kluger" Programmiererei gar nicht notwendig wäre.

von Sebastian S. (sebion7125)


Lesenswert?

Johann L. schrieb:
> Sebastian Steppeler schrieb:
>
>> hier kommt es auf ausführungsgeschwindigkeit
>> (jeder takt weniger zählt) an
>
> Und warum wird dann auf Codegröße optimiert?

Guter Einwand. Ich habe den schalter damals aus mehreren gründen auf 
diesem wert belassen: Erstens hab ich gelesen, dass diese einstellung 
empfohlen wird, da die anderen optimierungsstufen bei minimalem 
performancevorteil signifikant mehr code erzeugen, was bei mir auch 
stimmte, da der avr zu 150 % voll ist, wenn ich maximal auf 
geschwindigkeit -O3 optimiere und zweitens wusste ich damals nicht, dass 
ich die optimierung für einzelne quellcodedateien unterschiedlich setzen 
kann. Des weiteren habe ich den zeitkritischen code natürlich versucht 
möglichst effizient zu schreiben und ich denke so das meiste 
optimierungspotential schon ausgeschöpft.
Im moment fahre ich ganz gut mit der einstellung, sollte es nochmal eng 
werden(leider kann es das sowohl in der ausführungszeit als auch in der 
codegröße passieren, und ich versuche zu vermeiden einen größeren 
controller zu brauchen) hab ich ja immer noch einen optimierungsschalter 
den ich hochschalten kann.

von Karl H. (kbuchegg)


Lesenswert?

Sebastian Steppeler schrieb:

> Ihr müsst wissen die funktion stammt aus meinem sensorsystem fürs
> klavier ( http://sebion.wordpress.com ) und da das ding 8 kanäle a 1,2
> khz sample rate bewältigt

das sind 9.6kHz. Sollte noch gut bewältigbar sein.


> muss zumindest dieser teil des codes schnell
> gehen.

Was genau ist die Aufgabe dieses Codes?

(long ist nicht unbedingt ein angenehmer Zeitgenosse für deinen AVR. Und 
warum du hier unbedingt ein Vorzeichen brauchst, ist so auch nicht 
wirklich ersichtlich)

von Sebastian S. (sebion7125)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Sebastian Steppeler schrieb:
>
>> Ihr müsst wissen die funktion stammt aus meinem sensorsystem fürs
>> klavier ( http://sebion.wordpress.com ) und da das ding 8 kanäle a 1,2
>> khz sample rate bewältigt
>
> das sind 9.6kHz. Sollte noch gut bewältigbar sein.

Sagen wir es ist noch unter der schmerzgrenze. Immerhin läuft darauf 
eine kleine physikengine ;)

>> muss zumindest dieser teil des codes schnell
>> gehen.
>
> Was genau ist die Aufgabe dieses Codes?
>
> (long ist nicht unbedingt ein angenehmer Zeitgenosse für deinen AVR. Und
> warum du hier unbedingt ein Vorzeichen brauchst, ist so auch nicht
> wirklich ersichtlich)

Dieser code rechnet energiewerte in midi werte um und die Energie kann 
in diesem fall auch negativ werden, was aber bei der umrechnung keine 
rolle mehr spielt, da bei negativen werten die berechnung abgebrochen 
wird. ich brauch leider die 32 bit um keine numerischen ungenauigkeiten 
zu riskieren. Aber keine sorge ich weiß was ich tue und der code 
funktioniert ja auch prima, es muss nur noch das eine oder andere 
feature ergänzt werden.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> Habe gerade mal den gcc4.5.1 gegengetestet, [...] aber wie gesagt
> der code ist 10 % größer

Der neueste Win-Build ist vermutlich da:
Beitrag "Re: neue Windows-AVR-Toolchain für Atmega, Atxmega"

Was mich interessiert ist, welche Codegrößen du mit den Compilern 
verschiedenen Compilerversionent (wohl 4.3, 4.5 und 4.7) erreicht hast.

Bei der 4.7 helfen u.U auch die Optionen von

http://sourceforge.net/apps/mediawiki/mobilechessboar/index.php?title=Avr-gcc

von Sebastian S. (sebion7125)


Lesenswert?

Johann L. schrieb:
> Sebastian Steppeler schrieb:
>
>> Habe gerade mal den gcc4.5.1 gegengetestet, [...] aber wie gesagt
>> der code ist 10 % größer
>
> Der neueste Win-Build ist vermutlich da:
> Beitrag "Re: neue Windows-AVR-Toolchain für Atmega, Atxmega"
>
> Was mich interessiert ist, welche Codegrößen du mit den Compilern
> verschiedenen Compilerversionent (wohl 4.3, 4.5 und 4.7) erreicht hast.
>
> Bei der 4.7 helfen u.U auch die Optionen von
>
> http://sourceforge.net/apps/mediawiki/mobilechessboar/index.php?>title=Avr-gcc

Also wie gesagt der 4.7.0 hatte das beste resultat, darum bin ich jetzt 
auch mal pauschal auf den umgestiegen.

Die codegrößen schauen bei meinem projekt so aus:
4.3.2: 12392 bytes
4.3.3: 12388 bytes
4.5.1: 13538 bytes (avrstudio 5 version)
4.7.0: 11710 bytes

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:
> Also wie gesagt der 4.7.0 hatte das beste resultat, darum bin ich jetzt
> auch mal pauschal auf den umgestiegen.

Wie kann man im AVR-Studio 4 "pauschal" darauf umsteigen? Ich habe es 
momentan nur geschafft, indem ich unter

   Project -> Configuration Options -> Custom Option

das Häkchen "Use WinAVR" gelöscht habe und die Pfade auf den 4.7er 
avr-gcc bzw. make manuell eingetragen habe.

Aber das ist ja projektspezifisch. Wie kann man das im AVR Studio 4 
pauschal umstellen, so dass es für alle Projekte gilt?

von Sebastian S. (sebion7125)


Lesenswert?

Entschuldigung so habe ich das wort pauschal nicht gemeint ;)
Ich meinte bloß dass ich mal davon ausgehe, dass die neuste version mir 
keine zusätlichen fehler machen wird und ich darum zumindest für dieses 
projekt darauf umsteige.

Ich benutzte avrstudio 5 und da kann man das in der tat generell für 
alle projekte einstellen und für jedes einzelne projekt ausnahmen 
konfigurieren.

Bei avrstudio 4 hab ich keine ahnung, da ich es seit geraumer zeit nicht 
mehr nutze, aber so weit ich mich erinnere musste man da doch winavr 
separat runterladen und in avrstudio dann sagen wo sich winavr befindet? 
oder machte man das für jedes projekt einzeln? sorry ist schon zu lang 
her.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> Ich benutzte avrstudio 5 und da kann man das in der tat generell für
> alle projekte einstellen und für jedes einzelne projekt ausnahmen
> konfigurieren.

Ich habe es jetzt so gelöst, dass ich die Toolchain nach Backup des 
WinAVR20100110-Verzeichnisses einfach dorthinein kopiert habe und damit 
alle älteren Dateien überschrieben habe. So brauche ich keine 
Umstellungen zu machen.

> Bei avrstudio 4 hab ich keine ahnung, da ich es seit geraumer zeit nicht
> mehr nutze, aber so weit ich mich erinnere musste man da doch winavr
> separat runterladen und in avrstudio dann sagen wo sich winavr befindet?
> oder machte man das für jedes projekt einzeln? sorry ist schon zu lang
> her.

Man muss wohl WinAVR zuerst installieren und erst dann AvrStudio. Das 
Ding findet dann wohl automatisch WinAVR. Ist bei mir aber auch zu lang 
her. Ich habe jedenfalls nichts gefunden, um das nachträglich noch 
umzustellen. Änderung von PATH reichte jedenfalls nicht.

Gruß und Dank,

Frank

von Sebastian S. (sebion7125)


Lesenswert?

mal ne dumme frage, gibts nen grund warum du noch an avrstudio4 hängst?
die 5er version ist doch viel komfortabler. Das einzige was mir nen 
bisschen fehlt sind die fenster, die einem die register zeigen, muss man 
deren namen nicht immer auswendig wissen ;)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:
> mal ne dumme frage, gibts nen grund warum du noch an avrstudio4 hängst?

Ich mag kleine, aber feine Lösungen :-)

Ich habe hier desöfteren in der Vergangenheit gelesen, dass AvrStudio 5 
ein riesengroßes Monster ist. Die Größe von über 600 MB des 
Full-Installers hat mich da schon ein wenig abgeschreckt. Ausserdem 
liebe ich es, wenn Programme schnell starten. Da ist zum Beispiel ein 
Riesen-Unterschied zwischen MS Visual C++ Express 2008 und 2010. Ich 
habe beide installiert und wähle meist die 2008er. Die ist schneller 
gestartet ;-)

> die 5er version ist doch viel komfortabler.

Alles, was ich brauche, ist ein Editor und F7 zum Compilieren ;-)

Ausserdem denke ich mir, dass wenn ich jemandem meine Projekte anbiete 
(z.B. IRMP), es für ihn leichter ist, wenn er 4er Projekt-Dateien 
bekommt. Im Zweifel kann man diese mit beiden Programmversionen laden - 
umgekehrt geht das bestimmt nicht mehr.

> Das einzige was mir nen
> bisschen fehlt sind die fenster, die einem die register zeigen, muss man
> deren namen nicht immer auswendig wissen ;)

Das Fenster hat mich noch nie interessiert, das ist bei mir 
standardmäßig zu.

Aber Scherz beiseite: Ich habe mir heute morgen mal die 5.1er Version 
von AvrStudio heruntergeladen und werde das mal am Wochenende 
ausprobieren.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Frank M. schrieb:

> Ausserdem denke ich mir, dass wenn ich jemandem meine Projekte anbiete
> (z.B. IRMP), es für ihn leichter ist, wenn er 4er Projekt-Dateien
> bekommt. Im Zweifel kann man diese mit beiden Programmversionen laden -
> umgekehrt geht das bestimmt nicht mehr.

Das ist 4er oder 5er die Wahl zwischen Pech und Schwefel ;-)

Wenn du keine künstlichen Hürden durch Erfordernisse an die 
Build-Umgebung aufbauen willst,  dann liefere Makefiles mit aus. Punkt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> Die codegrößen schauen bei meinem projekt so aus:
> 4.3.2: 12392 bytes
> 4.3.3: 12388 bytes
> 4.5.1: 13538 bytes (avrstudio 5 version)
> 4.7.0: 11710 bytes

Danke für die Werte.

Das würde bedeuten, daß 4.5 um über 15% größeren Code generiert als 4.7. 
Irgendwie kann ich garnicht glauben, daß der sooo schlecht ist.

...andererseits war die ständig mieser werdende Codequalität von avr-gcc 
4.x der Grund für mich, mal ins Getümmel der GCC-Entwicklung 
reinzuschnuppern.


Jörg Wunsch schrieb:

> Das [der Compiler] ist doch kein Mensch, sondern der arbeitet
> formale Algorithmen ab.

Was zur Frage führt, was denn ein Mensch "abarbeitet", wenn er ein 
(Assembler-)Programm schreibt ;-)

Optimierende Compiler suchen ihr Glück ja darin, den Code quasi zu 
vaporisieren und in hunderten von Passes die Ursuppe neu zu arrangieren 
um schliesslich das Compilat zu erhalten.

Ein Compiler arbeitet also quasi auf der Ebene von Elekronen und Quarks, 
wo Menschen auf der Ebene von Molekülen, Kristallen oder noch 
komplexeren Strukturen denken.

Wenn es zum Beisipel darum geht, Instruktionen geschickt zu kombinieren 
um bestimmte Aktionen darzustellen, dürften Menschen einem Compiler 
haushoch überlegen sein — insbesondere dann, wenn der Instruktionssatz 
nicht-orthogonal ist oder viele schwer zu beschreibende Instruktionen 
beinhaltet, die ein Compiler nur schwerlich (optimal) einsetzen kann. 
Typische Beispiele sind Bitgefummel oder geschickte Kombination 
arithmetischer Befehle, etwa bei Divisionsroutinen.

Zumindest solange nicht zu viele Instruktionen dazu notwendig sind. In 
diesem Fall würde ein Mensch das Problem in kleinere Einheiten 
aufteilen, diese nahezu optimal lösen, und die Einzellösungen dann 
puzzleartig zusammensetzen; wobei der Hauptaufwand/Performanceverlust 
beim Zusammenkitten liegt, d.h. bei Registergeschubse oder anderwärtiger 
Resourcenverwaltung.

Ein Compiler rührt jedoch einfach nur im Codebrei seiner Ursuppe, mit 
lediglich ungefähren Anhaltspunkten und Heuristiken ob die Aktionen in 
einem Pass denn nun günstig sind oder nicht.  Liegt die Heuristik 
daneben, liegt das Kind im Brunnen und bleibt auch da liegen, weil 
nachfolgende Passes eine ungünstige Transformation oftmals nicht 
rüchgängig machen können.

Der Multi-Pass Ansatz entspricht also in etwa einem Nebelspaziergang, 
bei dem man sich in einer nur schemenhaft erkannbaren Umgebung bewegt 
und ein besonders interessantes Ziel anzusteuern hat.

Und sowas wie spekulative Compilierung mit Verfolgung unterschiedlicher 
Pfade und Auswählen des besten gibt es auch nicht. Einfach deshalb, weil 
niemand stundenlang warten will, bis der Compiler endlich das stolze 
Resultat präsentiert nachdem er tausende von unterschiedlichen 
Kombinationsmöglichkeiten durchexerziert und wieder verworfen hat.  Bei 
Schachcomputern, die auf Weltklasseniveau spielen, ist man lange 
Antwortzeiten gewohnt — aber sowas bei einem Compiler? Und dann auch 
noch bei jedem Modul? Nur, um nach der nächsten Quelländerung wieder 
alles durchnudeln zu lassen und das verherige Compilat Makulatur ist?

Ob bei Compilerbau/-theorie anderer Ansätz wie menschenähnlichere 
Problemlösungsstrategien verfolg oder überhaupt Gegenstand der Forschung 
sind, weiß ich nicht.  Die Forschung scheint sich momentan eher in 
Richtung formaler Verifizierbarkeit und neuer 
Transformationen/Darstellungen für das Vaporisier-Modell zu bewegen.

von Uwe (de0508)


Lesenswert?

Sebastian

würdest Du bitte noch die Compiler- und Linkoptionen nennen, bei denen 
Du diese Ergebnisse erhalten hast ?

Sebastian Steppeler schrieb:
> Also wie gesagt der 4.7.0 hatte das beste resultat, darum bin ich jetzt
> auch mal pauschal auf den umgestiegen.
>
> Die codegrößen schauen bei meinem projekt so aus:
> 4.3.2: 12392 bytes
> 4.3.3: 12388 bytes
> 4.5.1: 13538 bytes (avrstudio 5 version)
> 4.7.0: 11710 bytes

ich verwende avr-gcc 4.3.5 und den avr-gcc gcc-4.6.3 alternativ unter 
ubuntu.

Mit diesen Einstellungen/ Optionen verändere/ optimiere ich meine 
Projekte:
1
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
2
CFLAGS += -Wall -Wstrict-prototypes
3
#CFLAGS += -Wchar-subscripts -Wsign-compare
4
## Zusatz Flags
5
CFLAGS += -fno-inline-small-functions
6
#CFLAGS += -finline-small-functions
7
#CFLAGS += -fno-split-wide-types
8
#CFLAGS += -fno-tree-scev-cprop
9
CFLAGS += -fno-move-loop-invariants
10
#CFLAGS += -mcall-prologues
11
#
12
## !!Danach hat man nur noch 256 Byte Stack !!
13
#CFLAGS += -mtiny-stack
14
# --
15
CFLAGS += -ffunction-sections -fdata-sections
16
CFLAGS += -Wl,--gc-sections
17
CFLAGS += -Wl,--relax
18
# avr-gcc 4.6.0 
19
#CFLAGS += -fno-strict-aliasing
20
#CFLAGS += -flto

Device: atmega8
# avr-gcc 4.3.5
Program:    5578 bytes (68.1% Full)
(.text + .data + .bootloader)

Data:        179 bytes (17.5% Full)
(.data + .bss + .noinit)

# avr-gcc 4.6.0
Program:    5594 bytes (68.3% Full)
(.text + .data + .bootloader)
Data:        183 bytes (17.9% Full)
(.data + .bss + .noinit)

von Sebastian S. (sebion7125)


Lesenswert?

Uwe S. schrieb:
> Sebastian
>
> würdest Du bitte noch die Compiler- und Linkoptionen nennen, bei denen
> Du diese Ergebnisse erhalten hast ?
>

hatte für alle versionen das hier eingestellt:

compiler: -funsigned-char -funsigned-bitfields -DF_CPU=16000000UL  -Os 
-ffunction-sections -fpack-struct -fshort-enums -Wall -c -gdwarf-2 
-std=gnu99  -fdata-sections  -mmcu=atmega16


linker: -Wl,-lm  -Wl,--gc-sections  -mmcu=atmega16

habe dann mal noch -fno-split-wide-types -mcall-prologues
und -Wl,--relax hinzugefügt und kam auf 11156 mit dem gcc 4.7.0

was hat es eigentlich mit dem tinystack flag auf sich? da steht im 
manual dass nur das LSbyte vom stackpointer geändert wird, aber ich 
dachte immer der stackpointer wird von push und pop automatisch 16 bit 
lang geändert?!?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:
> was hat es eigentlich mit dem tinystack flag auf sich?

Ist eine Option, die es besser gar nicht geben sollte.

von Sebastian S. (sebion7125)


Lesenswert?

kann ich mir gut vorstellen, aber was tut sie denn? bei mir verkleinert 
sie den code um ein paar bytes, aber ich lasse vorsichtshalber mal die 
finger davon.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:
> aber was tut sie denn?

Dann, wenn der Stackpointer zu manipulieren ist (für das Einrichten
eines Stackframes) nur SPL statt SPL + SPH modifizieren.

Für die Controller, bei denen es nur SPL gibt, sollte das der
Compiler inzwischen automatisch ordentlich machen, und für alle
anderen ist die Option einfach schlicht nur gefährlich.

Ähnlich unsinnig und gefährlich ist -mshort-calls.  Dummerweise hat
irgendjemand bei AVR Studio 5 dafür noch eine Checkbox mit eingebaut. 
:-(

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> hatte für alle versionen das hier eingestellt:
>
> -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums

Diese Optionen verändern das ABI (Binary Interface) des Codes. Wenn sich 
das positiv auf Codegröße auswirkt dann ist das ein angenehmer 
Seiteneffekt, zu den Optimierungsoptionen sind diese Schalter aber nicht 
zu zählen.

> -Os

Unter den genannten Optionen die einzige, die wirklich was an den 
verwendeten Optimierungsalgorithmen dreht. Ein Link auf weitere Optionen 
hatte ich oben schon genannt.

> -ffunction-sections -fdata-sections

-fdata-sections ist in neueren avr-gcc zweischneidig. Man kann sich 
entscheiden zwischen -fdata-sections und Constant Merging. Konstanten in 
unterschiedlichen Sections können logischerweise nicht gemergt werden.

> was hat es eigentlich mit dem tinystack flag auf sich?

Das wird eigentlich nur compilerintern verwendet um die Erzeugung der 
Multilibs zu steuern und den jeweiligen Multilib-Default für einen 
bestimmten µC auszuwählen, nämlich abhängig davon, ob der SP des µC 8 
oder 16 Bits groß ist.

Zwar kann man das Flag auch von Hand setzen und einen 16-Bit SP so 
behandeln lassen als hätte er nur 8 Bit, d.h. SPH wird als 0 angenommen, 
aber davon würd ich wie oben bereits gesagt abraten. Zum Beipiel kümmert 
sich der Startup-Code nicht um dieses Flag, initialisiert also SPH für 
16-Bit µC mit einem Wert ≠ 0.

von Peter (Gast)


Lesenswert?

Der GCC mach noch andere unsinnige Sachen.

Ein Port zugriff wird erst an einer höheren Optimierung  zu einem 
Befehl.
Vorher holt er das Port Register, macht die Operation und schreibt dann 
das Register wieder zurück.

Geil ist das wenn man im Interrupt auf den selben Port zugreift.
Da kannst Du dich dumm und dusselig suchen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter schrieb:

> Ein Port zugriff wird erst an einer höheren Optimierung  zu einem
> Befehl.
> Vorher holt er das Port Register, macht die Operation und schreibt dann
> das Register wieder zurück.
>
> Geil ist das wenn man im Interrupt auf den selben Port zugreift.
> Da kannst Du dich dumm und dusselig suchen.

Du meinst sowas?
1
PORTD |= 1;
Bitte zeige, wo im Sprachstandard zugesichert wird, daß dies in eine 
einzige Maschineninstruktion zu übersetzen ist!

Daß avr-gcc das bei höheren Optimierungen macht ist ein Bonbon, mehr 
nicht. Wenn Code davon ausgeht, daß dies immer der Fall ist, ist der 
Code schlicht und einfach nicht robust denn er macht Annahmen, die 
nirgends spezifiziert sind!

von Sebastian S. (sebion7125)


Lesenswert?

Johann L. schrieb:

> Du meinst sowas?
>
1
PORTD |= 1;
> Bitte zeige, wo im Sprachstandard zugesichert wird, daß dies in eine
> einzige Maschineninstruktion zu übersetzen ist!
> ...

Mh das ist interessant, vielleicht sollte man das mal ins avrgcc 
tutorial aufnehmen, dass man nicht davon ausgehen kann, das portzugriffe 
atomar sind. Würde ich als anfänger nämlich nie auf die idee kommen da 
nen fehler zu suchen...

von (prx) A. K. (prx)


Lesenswert?

Peter schrieb:

> Ein Port zugriff wird erst an einer höheren Optimierung  zu einem
> Befehl.

Bis auf die Tinys/Megas der ersten Generation (wie Mega8/16/32) sind die 
AVRs mittlerweile mit einer XOR-Funktion auf die Ports ausgestattet, 
indem nach PINx geschrieben wird. Das ermöglicht unabhängig von 
Portadresse und Optimierungsgrad atomare Pinoperationen.

Wenn man im Interrupt zwar den gleichen Port aber nicht die gleichen 
Pins anspricht kann man im Hauptprogramm
  PORTA &= ~mask;
  PORTA |=  mask;
durch
  PINA =  PORTA & mask; // vgl PORTA ^=  PORTA & mask
  PINA = ~PORTA & mask; // vgl PORTA ^= ~PORTA & mask
ersetzen. Alle Pins ausserhalb der Maske bleiben unbeeinflusst, auch 
wenn ein Interrupt zwischenrein funkt. Das funktioniert auch dann wenn 
"mask" keine Konstante ist.

Die Ersatzsequenz rechts ist hingegen nicht atomar.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
>> -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
>
> Diese Optionen verändern das ABI (Binary Interface) des Codes.

-fpack-struct ist ein no-op, der sich hartnäckig in allerlei
Makefiles hält.

Da der AVR keinerlei memory alignment constraints kennt, werden
struct tags immer auf Bytegrenzen ausgerichtet.

-funsigned-char und -funsigned-bitfields vertuschen bestenfalls
Fehler in schlampig programmiertem Code.  Wer es im dritten Jahr-
tausend immer noch nicht schafft, "char", "signed char" und
"unsigned char" sauber auseinander zu halten, dem gehört auf die
Finger geklopft.  Wer zu faul ist, seine bitfields als unsigned
zu deklarieren, wenn sie vorzeichenlos sein sollen, dem ist kaum
noch zu helfen.

-fshort-enums löst man besser durch ein _attribute_ am jeweiligen
enum.  Wenn man das projektweit (bspw. über eine Headerdatei)
konsitent durchzieht, dann ist man wenigstens nicht mehr davon
abhängig, dass alle Objektdateien des Projekts (auch bspw. die, die
aus Bibliotheken hinzu kommen) gleichermaßen mit dieser Option
compiliert worden waren.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Sebastian Steppeler schrieb:

> Mh das ist interessant, vielleicht sollte man das mal ins avrgcc
> tutorial aufnehmen, dass man nicht davon ausgehen kann, das portzugriffe
> atomar sind. Würde ich als anfänger nämlich nie auf die idee kommen da
> nen fehler zu suchen...

Das steht wie gesagt in keinem Sprachstandard (und auch in keiner 
ABI/EABI).

Ich würde mal davon ausgehen, daß das avr-gcc Tutorial nicht die 
Intention hat, ein komplettes C Tutorial zu sein. Dann bräuchte man zig 
C-Tutorials: Eines im avr-gcc, eines im arm-gcc, eines im avr32-gcc, 
eines im...

Das avr-gcc Tutorial kann sich darauf beschränken zu erklären, was über 
den Standard hinaus geht oder implementation defined ist wie:

• Wie groß ist ein short, long, ...?
• Wie groß sind Zeiger?
• Wie Definiert man eine ISR?
• Wie greift man auf SFRs zu?
• Wie macht man einen Codeausschnitt atomar?

Das war's auch schon. Wobei die letzten beiden Punkte lediglich 
Anwendung von AVR-Libc Makros bedeutet und absolut nichts speziell mit 
avr-gcc zu tun haben.

Dinge wie Timer-Benutzung, ADC-Verwendung, etc. sind auch nicht avr-gcc 
spezifisch, Das einzige was man dazu braucht, ist:

• Wissen, wie man auf SFRs zugreift
• Wissen, wie man I/O-Module bedienent, d.h. Handbuch lesen

Ob die SFRs nun in Atmel-Assembler, in GNU-Assembler, in C mit avr-gcc, 
in C mit Imagecraft, in C mit IAR, ... bedient werden ist doch sowas von 
Wurscht. Die Vorgehensweise ist immer gleich, und ich verstehe nicht, 
warum das x-mal an unterschiedlichen Stellen immer und immer wieder 
durchgekaut wird.

Stattdessen wäre es viel sinnvoller, Tutorials für einzelne Module wie 
ADC oder Timer zu haben, die unabhängig von einer bestimmten Sprache 
sind und vielmehr das Modul und seine Bedienung und Möglichkeiten 
erklären als auf Geklimper mit einzelen Kommandos abzufahren, die eine 
klare Sicht eher verstellen als befördern.

Wer nicht in der Lage ist, diese Transferleistung zu erbringen, wie ein 
SFR nun in einer konkreten Sprache zu bedienen ist, ist einfach noch 
nicht bereit für die Aufgabe, finde ich.

von Sebastian S. (sebion7125)


Lesenswert?

Johann L. schrieb:

> Das avr-gcc Tutorial kann sich darauf beschränken zu erklären, was über
> den Standard hinaus geht oder implementation defined ist wie: ...

Im Prinzip könnte es das, tut es aber gottseidank nicht wirklich, denn 
dann hätte ich daraus nicht so viel gelernt und mir vieles erstmal 
zusammensuchen müssen. Als ich mit der avr programmiererei begonnen habe 
hatte ich nämlich von interrupts und atomaren zugriffen garkeinen 
schimmer (hatte davor immer nur windows/directx/opengl anwendungen 
geschrieben und musste mich mit multithreading oder interrupts nie 
wirklich auseinandersetzen). Auch das berühmte "volatile" war mir bis 
dato immer völlig schleierhaft gewesen. Aber gerade soetwas wie 
interrupt handling ist doch gerade im avr bereich ein ganz wichtiges 
thema, was wie ich finde in dem tutorial sehr gut behandelt wird. Für 
anfänger sicher unverzichtbar.
Ob das thema mit dem atomaren portzugriff da reingehört, da kann man 
sicher drüber streiten.

von Sebastian S. (sebion7125)


Lesenswert?

Johann L. schrieb:
> Stattdessen wäre es viel sinnvoller, Tutorials für einzelne Module wie
> ADC oder Timer zu haben, die unabhängig von einer bestimmten Sprache
> sind und vielmehr das Modul und seine Bedienung und Möglichkeiten
> erklären als auf Geklimper mit einzelen Kommandos abzufahren, die eine
> klare Sicht eher verstellen als befördern.

Da gebe ich dir prinzipiell recht, aber letzlich tut das avrgcc tutorial 
das doch fast. Man muss sich die codebeispiele doch nur wegdenken und 
wenn man nur eine beschreibung der funktionseinheit und deren 
grundsätzliche bedienung haben will, dann liefert das datenblatt gerade 
bei atmels avrs das doch recht gut.

von Peter (Gast)


Lesenswert?

Tutorial hin oder her.

Fakt ist das jeder andere Compiler erkennt das ein Portzugriff gemacht 
werden soll und gleich den entsprechenden Atomaren Befehl dafür nimmt.
Wenn man nun vom IAR her kommt und auf den GCC umsteigt, kann es zu 
Problemen kommen und man versteht erst mal nicht warum, denn der Code 
hatte ja mit dem IAR funktioniert.


Was ist nun das richtige (habe gerade keinen GCC zur Hand)?
  PORTA &= ~mask;
oder
  PORTA =  PORTA & mask;    // PINA kann ich nichts zuweisen
oder
 PORTA ^=  PORTA & mask

von (prx) A. K. (prx)


Lesenswert?

Peter schrieb:

>   // PINA kann ich nichts zuweisen

Bei einem ATtiny2313 oder ATmega88 kannst du das sehr wohl.

von (prx) A. K. (prx)


Lesenswert?

Peter schrieb:

> Fakt ist das jeder andere Compiler erkennt das ein Portzugriff gemacht
> werden soll und gleich den entsprechenden Atomaren Befehl dafür nimmt.

Aber auch nur dann, wenn der Port bitadressierbar ist. Was bei AVRs in 
grösseren Gehäusen nicht unbedingt auf alle Ports zutrifft. Migration 
von einem Port zu einem anderen kann also ebenfalls überraschen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter schrieb:
> Tutorial hin oder her.
>
> Fakt ist das jeder andere Compiler erkennt das ein Portzugriff gemacht
> werden soll und gleich den entsprechenden Atomaren Befehl dafür nimmt.

Das macht avr-gcc ja auch.

Wenn du aber -O0 setzt und damit explizit anforderst, GCC im 
Erbsenzähl-Modus zu betreiben, dann bekommst du das auch. Konkret: Ein

PORTB |= 1;

sind 3 Operationen: Ein Laden, ein OR und ein Schreiben.

> Wenn man nun vom IAR her kommt und auf den GCC umsteigt, kann es zu
> Problemen kommen und man versteht erst mal nicht warum, denn der Code
> hatte ja mit dem IAR funktioniert.

Das hat wie gesagt weniger mit dem Verständnis des Compilers zu tun, 
sondern mit dem Verständnis der Sprache und welche (nicht erfüllten) 
Implikationen man daraus ableitet.

Daraus, daß "etwas funktioniert", lässt sich nichts über die Robustheit 
von Code ableiten. Siehe die schlottrige (Nicht-)Verwengung von 
volatile, Nicht-Beachtung von Strict Aliasing, etc. in altem Code, was 
dann in neueren Compilerversionen oder mit Optimierung zu Problemen 
führt.

Gerne wird dann pauschal über "Compilerfehler" schwadroniert.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:

> Daraus, daß "etwas funktioniert", lässt sich nichts über die Robustheit
> von Code ableiten. Siehe die schlottrige (Nicht-)Verwengung von
> volatile, Nicht-Beachtung von Strict Aliasing, etc. in altem Code

In neuem auch... Ich hatte dazu mal einen wenig ergiebigen kurzen Disput 
mit einem Spezi von Coocox, deren RTOS Source Code völlig ohne 
"volatile" auskam (Barriers fand ich auch nicht). Mir schien das sehr 
nach dem Motto "wieso, es funktioniert doch" gestrickt zu sein. Die 
Antwort auch.

von Peter (Gast)


Lesenswert?

Nun so pauschal würde ich dann nicht "geht doch" sagen.
An die Regeln der Programmiersprache muss ich mich schon halten.

Es ist halt nur schade das alle AVR Compiler es vernünftig umsetzen, nur 
der GCC erst ab einer höheren Optimierung.
Das es AVRs gibt bei denen das nicht bei jedem Port geht war mir nicht 
bekannt, der grösse  bei mit ist der ATMega644.

Das man generell aufpassen muss was man wo macht sollte wohl klar sein.
Ob man nun im Interrupt auf Variablen zugreift oder IOS da kann was 
schief laufen.

Das mit dem Port zugriff war mir halt nur mal unangenehm aufgefallen, 
weil ein Timing nicht mehr passte nachdem ich den Compiler gewechselt 
hatte.
Da waren auch keine Interrupts aktiv da ich nur Daten über Pins raus 
geschoben hatte.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter schrieb:
> Nun so pauschal würde ich dann nicht "geht doch" sagen.
> An die Regeln der Programmiersprache muss ich mich schon halten.

Und wo steht da, also in der Sprachdefinition/spezifikation, daß diese 
oder jene Operation atomar oder durch bestimmte Maschinenbefehle 
abzubilden ist? Oder in welchem (E)ABI?

Und in welcher Spezifikation/(E)ABI steht, daß ein C-Befehl innerhalb 
einer bestimmten Zeit/Tickanzahl abgehandelt werden muss?

von Oliver (Gast)


Lesenswert?

Peter schrieb:
> Es ist halt nur schade das alle AVR Compiler es vernünftig umsetzen, nur
> der GCC erst ab einer höheren Optimierung.

Das hängt sehr stark von deiner Vorstellung von "vernünftig" ab. Wer 
unbedingt Code ohne Optimierung nutzen muß, dabei aber trotzdem auf 
"vernünftige" Optimierungen nicht verzichten kann, der muß gcc ja nicht 
nutzen. Dein "alle" impliziert ja,daß du da beliebeig viele Alternativen 
zu Wahl hast. gcc ist ein C-Compiler, kein Mikrocontroller-Compiler.

Oliver

von vn nn (Gast)


Lesenswert?

A. K. schrieb:
> Bis auf die Tinys/Megas der ersten Generation (wie Mega8/16/32) sind die
> AVRs mittlerweile mit einer XOR-Funktion auf die Ports ausgestattet,
> indem nach PINx geschrieben wird. Das ermöglicht unabhängig von
> Portadresse und Optimierungsgrad atomare Pinoperationen.

A. K. schrieb:
> Peter schrieb:
>
>>   // PINA kann ich nichts zuweisen
>
> Bei einem ATtiny2313 oder ATmega88 kannst du das sehr wohl.

Funktionieren tut es zwar, im Datenblatt ist PINx aber immer noch als 
nicht beschreibbar angegeben. Funktionsgarantie ist also nicht 
vorhanden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

vn nn schrieb:
> Funktionieren tut es zwar, im Datenblatt ist PINx aber immer noch als
> nicht beschreibbar angegeben.

Weil das PINx-Register selbst natürlich nicht beschreibbar ist;
das bildet ja den aktuellen Zustand der Eingänge ab.

> Funktionsgarantie ist also nicht
> vorhanden.

Doch.  Die XOR-Funktion ist an anderer Stelle dokumentiert, ihre
Funktion ist damit sehr wohl garantiert.  Wenn sie es nicht wäre,
hätte man das Feature ja gar nicht erst einbauen müssen.  Ein "kann
funktionieren, muss aber nicht" hülfe ja keinem Anwender.

von Vn N. (wefwef_s)


Lesenswert?

Jörg Wunsch schrieb:
> Doch.  Die XOR-Funktion ist an anderer Stelle dokumentiert, ihre
> Funktion ist damit sehr wohl garantiert.  Wenn sie es nicht wäre,
> hätte man das Feature ja gar nicht erst einbauen müssen.  Ein "kann
> funktionieren, muss aber nicht" hülfe ja keinem Anwender.

Wo konkret? Habe schon mehrmals danach gesucht, und dass in der Register 
description von PINx steht, dass dieses lediglich gelesen werden kann, 
wäre da alles andere als sinnvoll.

Jörg Wunsch schrieb:
> Weil das PINx-Register selbst natürlich nicht beschreibbar ist;
> das bildet ja den aktuellen Zustand der Eingänge ab.

Natürlich muss es beschreibbar sein. Dass der Schreibzugriff umgeleitet 
wird, ändert ja nichts daran, dass ich was reinschreibe.

Edit: Tatsächlich steht es z.B. im Datenblatt des ATTINY2313, dort sind 
auch die Register dementsprechend als beschreibbar gekennzeichnet. Bei 
den anderen oben genannten Controllern ist dies allerdings nach wie vor 
meines Erachtens nicht der Fall, darauf zu verweisen, dass dies bei 
allen neueren möglich wäre also nur bedingt zu empfehlen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

vn nn schrieb:
> Bei
> den anderen oben genannten Controllern ist dies allerdings nach wie vor
> meines Erachtens nicht der Fall, darauf zu verweisen, dass dies bei
> allen neueren möglich wäre also nur bedingt zu empfehlen.

Datenblatt ATmega48/88/168 (die alten, ohne "P"):
1
12.2.2 Toggling the Pin
2
3
  Writing a logic one to PINxn toggles the value of PORTxn, independent
4
  on the value of DDRxn. Note that the SBI instruction can be used to
5
  toggle one single bit in a port.

von (prx) A. K. (prx)


Lesenswert?

Bei denen mit "P" steht das natürlich auch drin.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:

> Datenblatt ATmega48/88/168 (die alten, ohne "P"):
>
> 12.2.2 Toggling the Pin
>
>   Writing a logic one to PINxn toggles the value of PORTxn, independent
>   on the value of DDRxn. Note that the SBI instruction can be used to
>   toggle one single bit in a port.

Nett.

Das bedeutet also, daß auf diesen Registern SBI komplett anders reagiert 
als IN/OR/OUT. Was wiederum bedeutet, daß der Compiler zum Beispiel 
keinesfalls

SFR |= 1;

als

SBI SFR,0

übersetzen darf da sich beide signifikant unterscheiden.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Das bedeutet also, daß auf diesen Registern SBI komplett anders reagiert
> als IN/OR/OUT.

SBI hat einfach nur keinen Sinn auf einem PINx.  Nein, es ist immer
noch (bis auf das Timing) Äquivalent zu IN/OR/OUT, aber auch das ist
dort sinnlos.

Ohne das eingebaute XOR-Feature würde halt die OUT-Phase komplett
ignoriert, mit dem Feature toggelt sie die PORTx-Pins, für die von
PINx eine 1 gelesen worden ist, zusätzlich das Pin, das hineine
verODERt worden war.

Bislang interessiert das den Compiler alles nicht.  Prinzipiell
könnte der Compiler jedoch im Wissen darum (und bei Kenntnis der
Controllertypen, in denen es implementiert ist) ein

PORTX ^= Y;

umsetzen als

*(&PORTX - 2) = Y;

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Johann L. schrieb:
>> Das bedeutet also, daß auf diesen Registern SBI komplett anders reagiert
>> als IN/OR/OUT.
>
> SBI hat einfach nur keinen Sinn auf einem PINx.  Nein, es ist immer
> noch (bis auf das Timing) Äquivalent zu IN/OR/OUT, aber auch das ist
> dort sinnlos.

IN/OR/OUT und SBI reagieren unterschiedlich, weil SBI nur ein Bit 
toggelt, während IN tatsächlich vom PIN SFR liegt und i.d.R mehr Bits 
gesetzt sind als im OR angegeben.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> IN/OR/OUT und SBI reagieren unterschiedlich, weil SBI nur ein Bit
> toggelt

Nein.  Auch SBI implementiert ein read-modify-write(*), nur eben nicht
unterbrechbar in einem Befehl.  Wenn man das auf ein PINx-Register
anwendet, würde also der gleiche Unfug rauskommen, weil das Ergebnis
davon abhängt, welche Bits man vom Eingaberegister bereits gelesen
hat und welche nicht.

(*) Aus diesem Grunde steht auch die ausdrückliche Warnung in den
Datenblättern:
1
· Bit 4 —­ ADIF: ADC Interrupt Flag
2
3
...
4
Beware that if doing a Read-Modify-Write on ADCSRA, a pending
5
interrupt can be disabled. This also applies if the SBI and CBI
6
instructions are used.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch schrieb:
> Johann L. schrieb:
>> IN/OR/OUT und SBI reagieren unterschiedlich, weil SBI nur ein Bit
>> toggelt
>
> Nein.  Auch SBI implementiert ein read-modify-write(*), nur eben nicht
> unterbrechbar in einem Befehl.  Wenn man das auf ein PINx-Register
> anwendet, würde also der gleiche Unfug rauskommen, weil das Ergebnis
> davon abhängt, welche Bits man vom Eingaberegister bereits gelesen
> hat und welche nicht.

Jetzt bin ich total verwirrt, denn oben zitiertest du:

>> Note that the SBI instruction can be used to
>> toggle one single bit in a port.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:

> Nein.  Auch SBI implementiert ein read-modify-write(*), nur eben nicht
> unterbrechbar in einem Befehl.  Wenn man das auf ein PINx-Register
> anwendet, würde also der gleiche Unfug rauskommen, weil das Ergebnis
> davon abhängt, welche Bits man vom Eingaberegister bereits gelesen
> hat und welche nicht.

Annahme: SBI PINA,0 entspricht der Sequenz
  temp = PINA;
  temp |= 1<<0;
  PINA = temp;
wovon jedenfalls der Compiler ausgeht, da er alle Operationen
  SFR |= bit;
im bitadressierbaren Bereich in
  SBI SFR,bit
übersetzt.

Wenn er das bei
  PINA |= 1<<0;
ebenfalls macht, dann führt dies zu einem anderen Ergebnis als
  SBI PINA,0;
Im ersten Fall werden alle Pins getoggelt, die bei Lesen von PINA eine 1 
lieferten, zzgl. Pin 0. Im zweiten Fall hingegen nur Pin 0.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Jetzt bin ich total verwirrt, denn oben zitiertest du:
>
>>> Note that the SBI instruction can be used to
>>> toggle one single bit in a port.

Stimmt natürlich, das würde dem widersprechen.

Ja, das ist verwirrend.

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.