Forum: Compiler & IDEs Frage bezüglich Optimierung


von Ulrich (Gast)


Lesenswert?

Weshalb erzeugt diese Zeile:
PORTB |= _BV(1)|_BV(2);
2Bytes mehr Code als diese 2 Zeilen:
sbi(PORTB, 1);
sbi(PORTB, 2);

Für sbi verwende ich das define von  <compat/deprected.h>

von Hagen R. (hagen)


Lesenswert?

im ersten Code muß der Compiler den PORTB in ein Register laden, danach 
mit einem Wert odern und zurück in PORTB schreiben

in  reg, PORTB
ldi temp, 3
or  reg temp
out PORTB, reg

du schreibst ja im C soure auch

PORTB = PORTB | (x);

im zweiten Falle kann der Compiler einen speziellen ASM Opcode nutzen 
der immr nur 1 Bit eines Ports gleichzeitig löschen/setzen kann.

Da dur nur 2 Bits auf diese weise änderst braucht man du 2 Befehle.

Angenommen do wolltest

PORTB |= _BV(1) | _BV(2) | _BV(3) | _BV(4) | _BV(5);

setzen, dann wäre deine 2'te Methode um ein Takt langsammer.

Gruß hagen

von Ulrich (Gast)


Lesenswert?

Bei mir benötigt aber z.B.  auch:
PORTB |= _BV(PB1)|_BV(PB2)|_BV(PB3)|_BV(PB4)|_BV(PB5);
im Gegensatz zu:
PORTB |= _BV(PB1)|_BV(PB2)|_BV(PB3)|_BV(PB4);
2 Bytes mehr...

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Hi

dein erster Fall dürfte eine fehlende Optimierung des GCC sein die eben 
nur zuschlägt wenn du zwei Bits setzt. Du kannst natürlich auch

PORTB |= _BV(1);
PORTB |= _BV(2);

schreiben. Das sollte zum gleichen Code wie die sbi Makros führen. Den 
zweiten Fall das

PORTB |= _BV(PB1)|_BV(PB2)|_BV(PB3)|_BV(PB4)|_BV(PB5);

mehr Code erzeugt als

PORTB |= _BV(PB1)|_BV(PB2)|_BV(PB3)|_BV(PB4);

kann ich hier (WINAVR 20060125) nicht nachvollziehen.

Matthias

von Rolf Magnus (Gast)


Lesenswert?

> dein erster Fall dürfte eine fehlende Optimierung des GCC sein die eben
> nur zuschlägt wenn du zwei Bits setzt.

Die Optimierung kann er möglicherweise schon, aber PORTB ist volatile. 
Das heißt, das bei

PORTB |= _BV(1) | BV(2);

das Register nicht mehr als einmal gelesen/geschrieben werden darf.

von Μαtthias W. (matthias) Benutzerseite


Lesenswert?

Rolf Magnus wrote:
>> dein erster Fall dürfte eine fehlende Optimierung des GCC sein die eben
>> nur zuschlägt wenn du zwei Bits setzt.
>
> Die Optimierung kann er möglicherweise schon, aber PORTB ist volatile.
> Das heißt, das bei
>
> PORTB |= _BV(1) | BV(2);
>
> das Register nicht mehr als einmal gelesen/geschrieben werden darf.

Echt? Ist volatile derart definiert? Wo steht denn das? (ernstgemeinte 
Frage!) Mir war volatile nur als modifier bekannt das sich der Wert der 
Variable auch außerhalb des aktuellen Programmfluß ändern kann und der 
Compiler deshalb einige Optimierung nicht vornehmen darf. Von einem 
einzigen Zugriff ist mir nichts bekannt. PORTB |= _BV(1) | BV(2); ist eh 
nich atomar.

Matthias

von Peter D. (peda)


Lesenswert?

Ich sach ma, der Ausdruck rechts vom = muß immer zuerst berechnet 
werden, unabhängig vom volatile.

Es ist durchaus ein sehr großer Unterschied, ob sich 2 Portpins 
gleichzeitig ändern oder nacheinander.

Daher darf hier nichts dahingehend optimiert werden, daß einer erst 
verspätet schaltet.

Z.B. ein SRG (74HC595) würde Dir da sehr böse sein.


Peter

von A.K. (Gast)


Lesenswert?

zu "volatile": <<This alters the translator to refetch the given 
identifier whenever it encounters an expression that includes the 
identifier. In addition, an object is marked as "volatile" must be 
stored at the point where an assignment to this object takes place.>>

Logischerweise gilt das auch umgekehrt. Wenn genau eine Zuweisung 
dasteht, darf der Compiler nicht zwei daraus machen. Das ANSI-Komitee 
hatte dabei nicht nur Exceptions sondern ausdrücklich auch I/O-Ports im 
Auge. Und bei manchen I/O-Ports macht es nun einmal einen gewaltigen 
Unterschied, ob man ein- oder zweimal reinschreibt (z.B. 
UART-Datenregister).

von A.K. (Gast)


Lesenswert?

@Peter: "Ich sach ma, der Ausdruck rechts vom = muß immer zuerst 
berechnet
werden, unabhängig vom volatile."

Nach dieser Definition wäre der Ausdruck
  a[i++] = b[i++];
wohldefiniert. Ist er aber nicht. Weder mit noch ohne "volatile". Eine 
Zuweisung ist kein "sequence point".

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


Lesenswert?

> Daher darf hier nichts dahingehend optimiert werden, daß einer erst
> verspätet schaltet.

Ja, genau daher sind die Portadressen ja auch als volatile definiert.
Sonst dürfte der Compiler dort nämlich Dinge zusammenfassen.

von Peter D. (peda)


Lesenswert?

@A.K.

ich meinte natürlich nur diese Art Ausdrücke mit Operand=

Bei

c = a + b + c;

ist die Reihenfolge undefiniert.

Aber bei

c += a + b;

muß a + b immmer zuerst berechnet werden.

Der Operand erbt quasi den Rang des = Zeichens.


Peter

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


Lesenswert?

Peter Dannegger wrote:

> Aber bei
>
> c += a + b;
>
> muß a + b immmer zuerst berechnet werden.

Peter, deine Erfahrungen in allen Ehren, aber gelegentlich solltest
du dich auch mit dem C-Standard auseinandersetzen.

Nein, a + b muss keinesfalls zuerst berechnet werden.  Der Compiler
hat genauso gut das Recht, obige Anweisungen als

c += a;
c += b;

zu implementieren, solange a, b und c wirklich einfache Variablen
sind, die nicht "volatile" sind.  Diffiziler wird es, wenn Funktionen
im Spiel sind, aber selbst bei

c += a() + b();

darf er das auch zu

c += b();
c += a();

umsortieren, wenn ich mich nicht gerade gründlich irre, da die
Reihenfolge der Bewertung der rechten Seite für den +-Operator nicht
vorgeschrieben ist.  (Andere Operatoren haben eine vorgeschriebene
Reihenfolge, insbesondere ||, && und der Komma-Operator.)  Ach so,
das geht nur so, wenn es keine Chance gibt, dass "c" einen Einfluss
auf die Funktionen a und b hat, also z. B. eine lokale Variable ist.
Bei einer globalen Variablen wäre wohl nur:

tmp = b();
tmp += a();
c += tmp;

zulässig, d. h. jede der Funktionen a und b muss noch den vorherigen
Wert von c sehen.

von Karl H. (kbuchegg)


Lesenswert?

> wenn ich mich nicht gerade gründlich irre

Du irrst nicht.
In C gibt es ja dieses seltsame Konstrukt des 'sequence
points'. Erst bei Erreichen eines Sequence Points müssen
alle Aktionen abgeschlossen sein. Bis allerdings der
sequence point erreicht ist, hat der Compiler alle
Freiheiten Berechnungen in beliebiger Reihenfolge
auszuführen, solange nur die Operatorreihenfolge
(und die Ausnahmen die Jörg bereits angeführt hat)
gewahrt bleibt.
Ob der Compiler bei einer Zuweisung zuerst die rechte
Seite auswertet (also den zugewiesenen Wert bestimmt)
oder die linke Seite (also die Adressauswertung, wo
der Wert abgespeichert werden soll), ist ganz alleine
ihm überlassen. Der Standard macht da keine Vorschriften,
= ist kein sequence point.

> zulässig, d. h. jede der Funktionen a und b muss noch den vorherigen
> Wert von c sehen.

Das wäre mit allerdings neu, dass sich der C-Standard um sowas
kümmert.
Wenn du mit globalen Variablen in Funktionen arbeitest, dann liegt
es am Programmierer sicherzustellen, dass die Funktionen auch
in der richtigen Reihenfolge aufgerufen werden. Das mit
'muss den vorherigen Wert von c sehen' stimmt also so nicht.

  c = a() +  b()

Wenn es also wichtig ist, dass tatsächlich a() vor b() aufgerufen
wird, dann kann man das nicht so schreiben, sondern muss explizit
eine Reihenfolge erzwingen:

  c = a();
  c += b();

Jetzt liegt zwischen dem Aufruf von a() und b() ein sequence
point, der verhindert, dass der Compiler den Aufruf von b()
vor dem Aufruf von a() machen kann.

> Bei einer globalen Variablen wäre wohl nur:
>
> tmp = b();
> tmp += a();
> c += tmp;
>
> zulässig,

Auch die andere Reihenfolge ist genauso zulässig. Genauso
wie

  c += a();
  c += b();

selbst wenn in a() und in b() c benutzt wird. Das ist das Problem
des Programmierers sich darum zu kümmern. Niemand sagt, dass
bei
  c += a + b;

die +=  Zuweisung eine atomare Einheit sein muss und nicht
auch auf Raten passieren kann. Die einzige Vorgabe die
der Compiler hat: Bei erreichen des ;-sequence points muss
die komplette Aktion abgeschlossen sein.

von A.K. (Gast)


Lesenswert?

>> zulässig, d. h. jede der Funktionen a und b muss noch den vorherigen
>> Wert von c sehen.

>Das wäre mit allerdings neu, dass sich der C-Standard um sowas
>kümmert."

Ein Funktionsaufruf ist ein solcher "sequence point" und deshalb kümmert 
sich der Compiler darum. Nicht darum ob erst a() dann b() oder 
andersrum, aber sehr wohl darum, dass c erst nach beiden Aufrufen seinen 
Wert ändert.

von Karl heinz B. (kbucheg)


Lesenswert?

> Ein Funktionsaufruf ist ein solcher "sequence point" und deshalb
> kümmert sich der Compiler darum.

Wo hatte ich nur meine Gedanken.
War wohl schon spät.

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.