mikrocontroller.net

Forum: Compiler & IDEs Spaßig: "% 16" vs. "& 0x0F"


Autor: johnny.m (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mal was Grundsätzliches zum AVR-GCC:

Ich benutze in einem Programm einen Zykluszähler (uint8_t count_cycles), 
der in einem Timer-Interrupt dekrementiert wird. Dieser Zykluszähler 
wird für zwei Auswertungen benötigt, von denen eine alle 256 Zyklen 
(Ausgabe auf Display) und die andere alle 16 Zyklen (Tasterabfrage) 
durchgeführt werden soll. Mit den 256 Zyklen ist keine Frage, da wird 
einfach der Zähler auf "0" überprüft. Die 16 Zyklen kann man prinzipiell 
mit
if(!(count_cycles % 16))
{
    //Tasterabfrage
}
hinbekommen. Ich hab mir dann gedacht, dass "x % 2^n" ja nichts anderes 
ist als "x & 0x0F". Und da das Programm für einen Laborversuch für 
Studis ist, die auch ein bisschen ein Gefühl dafür bekommen sollen, wie 
man welche arithmetischen oder logischen Operationen gegeneinander 
austauschen kann, um evtl. auch den Code effizienter zu gestalten, habe 
ich dann natürlich geschrieben
if(!(count_cycles & 0x0F))
{
    //Tasterabfrage
}
...und mir gedacht, dass das sicher eher weniger Assembler-Code gibt als 
die Schreibweise mit Modulo. Hab dann auch nicht weiter drüber 
nachgedacht. Irgendwann kam mir die spaßige Idee, einfach mal 
auszuprobieren, was der Compiler denn aus der Modulo-Version macht. Und 
da kam die große Überraschung: Die "& 0x0F"-Variante ergeben satte 8 
Bytes mehr Code als die mit "% 16"! Und ich war davon ausgegangen, 
dass eher weniger dabei rauskommt, aber auf keinen Fall mehr. Habe dann 
mal in die entsprechenden List-Files geschaut und folgendes gefunden:
  if(!(count_cycles & 0x0F))
  be:  89 2f         mov  r24, r25
  c0:  99 27         eor  r25, r25
  c2:  8f 70         andi  r24, 0x0F  ; 15
  c4:  90 70         andi  r25, 0x00  ; 0
  c6:  89 2b         or  r24, r25
  c8:  51 f4         brne  .+20       ; 0xde <__vector_3+0x48>
...für die &-Variante und
if(!(count_cycles % 16))
  be:  9f 70         andi  r25, 0x0F  ; 15
  c0:  51 f4         brne  .+20       ; 0xd6 <__vector_3+0x40>
...für Modulo. In beiden Fällen ist Optimierung -Os eingestellt.

Was mich jetzt wirklich verwundert, ist, dass der Compiler anscheinend 
aus der Modulo-Version exakt das macht, was ich mir vorgestellt habe, 
nämlich ein einfaches "& 0x0F". Was er allerdings aus der "& 
0x0F"-Version macht, leuchtet mir nicht ganz ein. Liegt das evtl. daran, 
dass die einfachen logischen und arithmetischen Operationen 
grundsätzlich mindestens in 16 Bit gerechnet werden? Und Division, 
Modulo usw. nur in 8 Bit? Wenn dem so ist, würde ich natürlich gerne 
generell wissen, welche Operationen in 16 und welche in 8 Bit 
verwurstet werden. Gerade wenn man solche Abfragen in zeitkritischem 
Umfeld macht, sollte man ja die kürzeste Variante nehmen.

Gruß

Johnny

Autor: A.K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Spezifikation ist immer die gleiche, d.h. die Rechnung muss so 
erfolgen, als ob sie 16bittig sei (int/unsigned). Es liegt am Compiler, 
ob er daraus den effizientesten Coder generiert oder nicht.

Die allgmeinen Optimierungsreglen in GCC sind naturgemäss nicht darauf 
aus, Wortoperationen durch Byteoperationen zu ersetzen. Das ist für fast 
alle Targets sinnlos. Das wird folglich irgendwie im AVR-spezifischen 
Teil abgewickelt. Und greift offensichtlich nicht immer.

Autor: Owz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo!

Was der Compiler wirklich draus baut ist schwer vorher zu sagen. Ich 
machs immer so, wenn ich über so was stolper, ich schau mir den Code 
ohne Optimierung an, dann kommt man oft drauf, was sich der Compiler 
dabei denkt!

mfg Owz

Autor: johnny.m (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
...Sollte oben natürlich heißen "x % 2^n" entspricht "x & 2^n" (und 
nicht "x & 0x0F")...

@Owz:
> ...ich schau mir den Code ohne Optimierung an...
Wieso ohne Optimierung? Sehe ich so keinen Grund für. Was der Compiler 
sich denkt, kann ich bei den o.g. Beispielen auch sehen. Ich sehe z.B. 
dass der Compiler bei "%" an 8 Bit denkt und bei "&" an 16 Bit... Oder 
meinst Du was anderes?

Autor: Rahul, der Trollige (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>...Sollte oben natürlich heißen "x % 2^n" entspricht "x & 2^n" (und
>nicht "x & 0x0F")...

Müsste es nicht eigentlich "x % 2^n" entspricht "x & 2^n-1" heissen?
Das Problem liegt doch darin, wie sich der Mensch, der den Compiler 
gebaut hat, die Umsetzung überlegt hat (Also eine Frage an Bruce und 
Jörg...). Vielleicht gibt es aber auch im C-Standard eine Regel, wie man 
das realisiert - davon habe ich natürlich keine Ahnung, da ich keine 
Compiler baue...
Interessant ist es aber trotzdem, vor allem, weil ich meine Puffer immer 
mit "& 2^n-1" begrenze...

Autor: johnny.m (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Rahul:
> Müsste es nicht eigentlich "x % 2^n" entspricht "x & 2^n-1" heissen?
Habs auch grad gemerkt. Bin noch nicht ganz wach.
for(int i = 0; i < 100; i++)
    printf("0Fh ist 15d und nicht 16d!!!!");

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Vielleicht gibt es aber auch im C-Standard eine Regel, wie man
> das realisiert

Die Regel ist ganz einfach:
Alles wird im größten Datentyp gerechnet, der im Ausdruck
vorkommt. Eine Zahl ohen weiteren Suffix ist ein int, es sei
denn die Zahl ist so gross dass sie nicht mehr in einen
int hineinpasst.

D.h. sowohl

   i & 0x0F

als auch

   i % 16

ist mal grundsätzlich als int abzuhandeln. Nun gibt es natürlich
noch den Passus Optimierung: Ein Compiler darf optimieren, solange
im Ergebnis nicht sichtbar ist, dass optimiert wurde.

Irgendwelche Leute haben sich jetzt überlegt, dass ein %16 auch
durch eine & Operation dargestellt werden kann. Sie haben sich
auch weiter überlegt, dass das Ergebnis nicht größer als 16 sein
kann und daher eine int-Operation überflüssig ist. Es reicht
wenn man das ganze auf dem low-byte macht. Es gibt viele
derartige Abkürzungen, schau dir nur mal an wie ein Compiler
Multiplikationen mit kleinen Zahlen realisiert.
Beim Fall &0x0F hat aber anscheinend noch niemand eine entsprechende
Optimierung eingebaut, die ähnliches leistet: den Compiler von
int auf char zurückschalten, weil das Ergebnis nicht größer
als char sein kann.

Autor: Fritz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ein anderer, käuflicher Compiler macht in beiden Fällen ein
andi r16,15.
Letztlich sind die paar Bytes mehr Code eher ein Schönheitsfehler.

Autor: A.K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Alles wird im größten Datentyp gerechnet, der im
> Ausdruck vorkommt.

... mindestens aber als "int" oder "unsigned".

Autor: johnny.m (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>> Alles wird im größten Datentyp gerechnet, der im
>> Ausdruck vorkommt.

> ... mindestens aber als "int" oder "unsigned".
Das war auch mein Kenntnisstand. Deshalb hatte ich mich ja gewundert, 
dass die Modulo-Operation anscheinend in 8 Bit gerechnet wird.

@Fritz:
Dass kommerzielle Compiler, die noch stärker auf die 8-Bit-CPUs 
zugeschnitten sind, da u.U. noch mehr optimieren, war mir auch schon 
aufgefallen (habe längere Zeit mit CodeVision programmiert).

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
johnny.m wrote:

>> ... mindestens aber als "int" oder "unsigned".

> Das war auch mein Kenntnisstand. Deshalb hatte ich mich ja gewundert,
> dass die Modulo-Operation anscheinend in 8 Bit gerechnet wird.

Der C-Standard ist ein "as if"-Standard: eine Implementierung muss sich
so verhalten, "als ob" sie die entsprechenden Vorschriften 1:1
implementiert hätte.  Wenn das obere Byte zum Ergebnis nicht beitragen
kann, darf es also aus der Berechnung auch weggelassen werden.

Autor: A.K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Dass kommerzielle Compiler, die noch stärker auf die 8-Bit-CPUs
> zugeschnitten sind, da u.U. noch mehr optimieren,

Bisweilen aber elegant am Standard vorbei. Die "mindestens als int" 
Regel wird bei propritären Compilern für 8-bit Microcontroller gerne 
verletzt, um besseren Code zu erzeugen. Üblicherweise findet sich dann 
eine Option, um zum Standard konform zu sein.

GCC kennt -mint8, damit ist sizeof(int)==sizeof(char). Die avrlib kannst 
du dann allerdings vergessen.

Autor: Matthias (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
...GCC kennt -mint8, damit ist sizeof(int)==sizeof(char). Die avrlib 
kannst
du dann allerdings vergessen....

Was ist damit gemeint??

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dass es möglich ist, int so umzudefinieren, dass es nicht
mehr 2 Bytes belegt sondern nur noch 1.
Was allerdings passiert, wenn du einen solchen int an eine
Funktion in der Standardlibrary übergibst, die 2 Bytes für
einen int erwartet, kannst du dir selbst ausmalen.

Autor: A.K. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mit -mint8 lässt sich der Compiler so einstellen, das "int" und 
"unsigned" 8-Bit-Typen sind. Die anderen Typen verschieben sich dann 
auch (=> GCC Manual, evtl. in 4.x anders als 3.x).

Der Code ist entsprechend effizienter, weil ebendiese erzwungenen und 
nicht immer wegoptimierten 8=>16-Bit Konvertierungen entfallen. 
Natürlich ist das nicht zum C-Standard konform, und alle hinzugelinkten 
Funktionen müssen ebenso übersetzt sein, jedenfalls solange sie in den 
Parametern und Return-Werten nicht durchgängig mit Typen wie "int16_t" 
arbeiten.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.