Forum: Compiler & IDEs Wie man volatile optimiert


von Gerd (Gast)


Lesenswert?

Ich treffe ab und zu mal auf irgendwelche NERDS die mit solchen Sprüchen 
kommen: "mit volatile sagt man dem Compiler, dass er nicht optimieren 
darf".

Dann denk ich mir: "Der hat lange nicht mehr programmiert"

(1) volatile = flüchtig
(2) Die betreffende Variable kann beispielsweise im Speicher oder in 
Memory-mapped Registern liegen (z.B. SPI-Controller, RX-Register)
(3) Die Variable kann sich von einer externen Quelle ändern.
(4) Wenn sich die Variable zwischen zwei Befehlen an ihrer Quelle ändern 
kann, dann darf der Compiler nicht auf einer lokalen Kopie in einem 
CPU-Register arbeiten:
So wird aus x++; x*=2; in PseudoCode:
   LDR x
   INC x
   STR x
   LDR x
   SHIFT x,1
   STR x
anstatt:
   LDR x
   INC x
   SHIFT x,1
   STR x

(5) volatile heißt nicht zwangsweise das Zugriffe darauf atomar sind.
Der Satz: "volatile verwendest du bei Variablen die du in einer 
Interrupt-Routine und im Programm verwendest" ist oberflächlich.
Denn der Befehl x++; ist nicht atomar.
Er besteht aus LDR, INC,STR. (Read, Modify, Write)
Nun kommt der Interrupt direct nach LDR rein und setzt die variable im 
Speicher auf (beispielsweise) Null. Direkt nach der Rückkehr wird aber 
noch der alte Wert inkrementiert und zurückgeschrieben.
Deswegen schreibt man variablen mit multiplen zugriff auch in einer 
nicht-unterbrechbaren Interrupt-Routine oder deaktiviert Interrupts für 
den Schreibzugriff.


Nun zu meiner Frage:

Die Sequenz
      volatile int a,x,y,z,
      a = x * y + z;
    kann doch optimiert werden?
      LDR x
      LDR y
      LDR z
      MADD x,y,z
      STR a

Der Befehl heißt Multiply-Add.

von (prx) A. K. (prx)


Lesenswert?

Ja.

von Gerd (Gast)


Lesenswert?

Gibt es noch andere eindrucksvolle Beispiele wo über volatile -Variable 
optimiert werden kann? Was ist mit dem ganzen SIMD-Zeugs?

von Klaus W. (mfgkw)


Lesenswert?

Gerd schrieb:
> Der Satz: "volatile verwendest du bei Variablen die du in einer
> Interrupt-Routine und im Programm verwendest" ist oberflächlich.

das Obige schließt das Folgende nicht aus:

> Denn der Befehl x++; ist nicht atomar.

atomar bzw. nicht atomar kann ein Problem sein, volatile
ist auch bei sonst atomaren Zugriffen manchmal nötig.

Wer also sagt, daß irgendwo volatile nötig ist, hat sich zu
atomar überhaupt nicht geäußert.

von Gerd (Gast)


Lesenswert?

Genau

von (prx) A. K. (prx)


Lesenswert?

Die obige Nerd-Aussage war natürlich etwas knapp formuliert. 
Entscheidend ist, was optimiert wird. Bei einer volatile Variablen 
darf der Compiler Zugriffe darauf nicht wegoptimieren und diese Zugriffe 
müssen innerhalb der entsprechenden sequence points stattfinden. Das 
wars dann aber auch schon. Der geladene Wert selbst ist nicht mehr 
volatile.

Freilich sollte der Zugriff auf die Variable in der Form erfolgen, wie 
er sich aus dem Programmtext ergibt. Wenn das also eine 32-Bit Variable 
ist, dann hat der Compiler die verdammte Pflicht, einen 32-Bit 
Lade/Speicherbefehl dafür zu verwenden (mal angenommen er hat einen). 
Auch wenn man nur ein Byte davon benötigt (in diese Falle tappen 
Compiler manchmal), oder man zweckmässigerweise gleich zwei zusammen in 
einem 64-Bit Befehl laden könnte. Das hat natürlich Folgen für SIMD 
Befehle.

Technischer Hintergrund: Spezielle Speichersysteme und Steuerregister 
können bestimmte Zugriffsbreiten erfordern, so dass beispielsweise ein 
Bytezugriff nicht zulässig ist.

von Peter D. (peda)


Lesenswert?

Gerd schrieb:
> Denn der Befehl x++; ist nicht atomar.

Da Du nicht eine bestimmte CPU genannt hast, ist diese Aussage falsch.
Z.B. der 8051 kann sehr wohl im internen SRAM "INC address" ausführen 
(auch DEC, XRL, ANL, ORL, DJNZ).
Und man kann sogar eine Variable atomar lesen und löschen (XCH).

Und wie schon gesagt wurde, volatile und atomar sind verschiedene Dinge. 
Sie werden nur oft zusammen benötigt.


Peter

von Gerd (Gast)


Lesenswert?

Hatte mal auf eine volatile variable aus einer Interrupt-Routine und der 
main aufgerufen. (UART-Treiber). Zugriffe waren nicht atomar. Die 
Wahrscheinlichkeit, dass das es zu einem Fehler kam lag bei etwa 
1:10000000 verarbeiteten Bytes. Was dann das Programm abschmieren ließ.
(Anstatt main+Int könnten das auch zwei threads sein)

Ein Testlauf gerät so zum Lottospiel. Man weiß ja vorher nicht, wieviele 
Daten man übertragen muss um auszuschließen, dass der Code funktioniert.
10000 Bytes? 1 Mio Bytes? 1GByte?

Dann sieht man bei gestandenen Programmieren mit 'jahrelanger' Erfahrung 
solche Fehler...

von David .. (volatile)


Lesenswert?

:(

von Peter D. (peda)


Lesenswert?

Mit der Implementierung von volatile bin ich auch etwas unglücklich. Man 
kann es nicht wie andere Typen casten, sondern es ist für eine Variable 
immer generell.
D.h. auch im Interrupthandler wird damit die Optimierung ausgeschaltet, 
also da, wo es am meisten wehtut.


Peter

von Gerd (Gast)


Lesenswert?

Denn der Befehl x++; ist nicht zwangsweise atomar.

Richtig.

von Gerd (Gast)


Lesenswert?

Die Sequenz:
  volatile i;
  ...
  if ((i-1)!=0) dosomething();

wird nicht mit (Pseudocode)
   LDR i
   dec i
   cmp i,0
   bz dosomething
sondern zu
   LDR i
   DJNZ dosomething
übersetzt?
Okay das Ergebnis von i-1 ist ja nicht mehr volatile und kann in einem 
Register gehalten werden? Also kann dort auch optimiert werden?

von Klaus W. (mfgkw)


Lesenswert?

ja, die verwendete Kopie von i ist nicht mehr volatile.

Aber falls i-1 kurz danach nochmal benötigt wird, muß es erneut 
berechnet werden.

von Michael B. (mb_)


Lesenswert?

Peter Dannegger schrieb:
> Mit der Implementierung von volatile bin ich auch etwas unglücklich. Man
> kann es nicht wie andere Typen casten, sondern es ist für eine Variable
> immer generell.

Die Aussage ist so ziemlich falsch.
Natürlich kann man volatile casten. Und genau das kann man verwenden 
damit der compiler besseren Code erzeugen kann.

Volatile ansich ist eine ziemlich undefinierte Sache. Es ist quasi 
Compilerabhängig was wirklich passiert.
Und selbst dann bringt es einem genau überhaupt keine Garantien über 
Sachen die zur Laufzeit passieren, wie runtime reordering. Das ist 
natürlich auf dem AVR kein Problem, weil es dort sowas nicht gibt.

Ich verwende in meinen Programmen volatile überhaupt nicht, sondern 
explizite memory-barriers. Eine barrier kann man beispielsweise so 
definieren:
1
#define mb()     __asm__ __volatile__("" : : : "memory")

Diese barrier funktioniert auf CPUs, die kein runtime reordering haben.
Wie man memory barriers verwendet kann man hier ganz gut nachlesen:
http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob_plain;f=Documentation/memory-barriers.txt;hb=HEAD

(Das Dokument beschreibt natürlich die ganze Geschichte. Mit CPUs die 
runtime reordering und SMP können. In der Situation würde volatile 
alleine komplett versagen.)

Mit der Verwendung von barriers hat der compiler wesentlich mehr 
Möglichkeiten den Code zu optimieren. Es ist halt nur auch komplizierter 
damit korrekten Code zu schreiben. Man muss halt erstmal verstehen wie 
das ganze funktioniert.

Edit meint: Bevor jemand meint ich hätte ja doch volatile in mb() 
verwendet: In gcc inline asm hat volatile eine andere und ganz klar 
definierte semantik.

von Peter D. (peda)


Lesenswert?

Michael Buesch schrieb:
> Die Aussage ist so ziemlich falsch.
> Natürlich kann man volatile casten.

Kannst Du uns auch verraten, wie?
Beim AVR-GCC funktioniert es nicht.

Bzw. nur über Macros von hinten durch die Brust ins Auge:
1
#define vu8(x)  (*(volatile uint8_t*)&(x))
2
#define vs8(x)  (*(volatile int8_t*)&(x))
3
#define vu16(x) (*(volatile uint16_t*)&(x))
4
#define vs16(x) (*(volatile int16_t*)&(x))
5
#define vu32(x) (*(volatile uint32_t*)&(x))
6
#define vs32(x) (*(volatile int32_i*)&(x))


Peter

von Michael B. (mb_)


Lesenswert?

Peter Dannegger schrieb:
> Michael Buesch schrieb:
>> Die Aussage ist so ziemlich falsch.
>> Natürlich kann man volatile casten.
>
> Kannst Du uns auch verraten, wie?
> Beim AVR-GCC funktioniert es nicht.
>
> Bzw. nur über Macros von hinten durch die Brust ins Auge:

Das Macros nur Textersetzung sind weißt du bestimmt.

volatile ist nur ein qualifier wie auch const. Das heißt man kann 
volatile genauso casten wie const.
Natürlich ergibt nicht jede cast möglichkeit korrekten Code. Aber das 
hat nichts mit volatile zu tun.

Projekte wie der Linux Kernel (und auch meine) treiben das Konzept auf 
die Spitze. Volatile wird dort nur über casts verwendet. Du wirst kein 
Datum im Kernel finden welches als volatile deklariert ist (außer 
vielleicht in 15 Jahre altem Code, den niemand mehr anfassen will. Wie 
beispielsweise einige elemente in diversen TTY strukturen, task_struct 
und 32bit-jiffies.).

Generell wird aber versucht zu vermeiden volatile überhaupt zu 
verwenden, weil die richtige Antwort oft nicht volatile sondern memory 
barrier heißt.

von Peter D. (peda)


Lesenswert?

Michael Buesch schrieb:
> volatile ist nur ein qualifier wie auch const. Das heißt man kann
> volatile genauso casten wie const.

Und warum geht es dann im AVR-GCC nicht?


Peter

von Michael B. (mb_)


Lesenswert?

Peter Dannegger schrieb:
> Michael Buesch schrieb:
>> volatile ist nur ein qualifier wie auch const. Das heißt man kann
>> volatile genauso casten wie const.
>
> Und warum geht es dann im AVR-GCC nicht?

Wtf? Du hast doch oben selbst geschrieben das es geht.
Außerdem verwendet die libc es in den I/O Definitionen (was auch korrekt 
ist).

von Peter D. (peda)


Lesenswert?

Michael Buesch schrieb:
> Wtf? Du hast doch oben selbst geschrieben das es geht.

Ich meinte natürlich, nicht so super umständlich über Pointer.
Da muß man ja für jeden Variablentyp ein extra Macro schreiben.
Wenn man dann noch Arrays, Unions und Structs hat, wird das ganz schön 
haarig.
Und nicht überall, wo man eine Variable einsetzen kann, kann man auch 
ein Macro verwenden.


Peter

von Klaus W. (mfgkw)


Lesenswert?

Peter Dannegger schrieb:
> Michael Buesch schrieb:
>> Wtf? Du hast doch oben selbst geschrieben das es geht.
>
> Ich meinte natürlich, nicht so super umständlich über Pointer.
> Da muß man ja für jeden Variablentyp ein extra Macro schreiben.
> Wenn man dann noch Arrays, Unions und Structs hat, wird das ganz schön
> haarig.

nicht, wenn man typeof nimmt.

von Michael B. (mb_)


Lesenswert?

Peter Dannegger schrieb:
> Michael Buesch schrieb:
>> Wtf? Du hast doch oben selbst geschrieben das es geht.
>
> Ich meinte natürlich, nicht so super umständlich über Pointer.
> Da muß man ja für jeden Variablentyp ein extra Macro schreiben.
> Wenn man dann noch Arrays, Unions und Structs hat, wird das ganz schön
> haarig.

Da gibts doch gar nichts "super umständliches" dran. Das ist ganz 
einfaches C. So einen cast sollte jeder lesen und schreiben und 
verstehen können, der behauptet C zu können.

Und was wird an arrays, unions und structs haarig? Gerade bei arrays 
gibts nichts haariges, wegen der array/pointer dualität.

Außerdem gibts typeof, wie schon gesagt, mit dem man sehr tolle makros 
machen kann, wenn man es denn unbedingt will.

> Und nicht überall, wo man eine Variable einsetzen kann, kann man auch
> ein Macro verwenden.

Ein Beispiel würde mich sehr interessieren.

von Rolf M. (rmagnus)


Lesenswert?

Peter Dannegger schrieb:
> Mit der Implementierung von volatile bin ich auch etwas unglücklich. Man
> kann es nicht wie andere Typen casten, sondern es ist für eine Variable
> immer generell.
> D.h. auch im Interrupthandler wird damit die Optimierung ausgeschaltet,
> also da, wo es am meisten wehtut.

Dazu kopiert man sie doch einfach am Anfang der ISR in eine lokale 
Variable und die dann am Ende zurück.

Michael Buesch schrieb:
> Außerdem gibts typeof, wie schon gesagt, mit dem man sehr tolle makros
> machen kann, wenn man es denn unbedingt will.

Das ist aber kein Standard-C, gibt's also nur auf manchen Compilern.
In C++ könnte man es elegaant und einfach über ein Template lösen, etwa 
so:
1
template<typename T>
2
volatile T& volatilize(T& value)
3
{
4
    return static_cast<volatile T&>(value);
5
}
6
7
int main()
8
{
9
    int x = 3;
10
    volatilize(x)++;
11
}

von Klaus W. (mfgkw)


Lesenswert?

Rolf Magnus schrieb:
> Das ist aber kein Standard-C, gibt's also nur auf manchen Compilern.

Stimmt, aber nachdem Programme dieser Art ohnehin kaum portabel
sind, kann man das meistens vertreten, denke ich.
Meine AVR-Programme werden nie auf etwas anderem als dem gcc
laufen, ebenso alles, was ich mit Threads unter Linux schreibe.

> In C++ könnte man es elegaant und einfach über ein Template lösen

Das ist richtig und ich verstehe auch nicht, wieso man nicht
einfach C++ nutzt, wo es nichts schadet.
Es hat an vielen Stellen Vorteile, wo man sich in C ziemlich
verbiegen muß.

von Peter D. (peda)


Lesenswert?

Michael Buesch schrieb:
> Außerdem gibts typeof, wie schon gesagt, mit dem man sehr tolle makros
> machen kann, wenn man es denn unbedingt will.

Danke, funktionert. Jetzt brauche ich nur noch ein Macro.
typeof ist ja dann auch bequem, um Pointer anzulegen.


> Ein Beispiel würde mich sehr interessieren.

Habe ich mir leider nicht gemerkt.
Die Fehlermeldung könnte "lvalue required" gewesen sein.


Peter

von Michael B. (mb_)


Lesenswert?

Rolf Magnus schrieb:
> Das ist aber kein Standard-C, gibt's also nur auf manchen Compilern.

Jo ganz klar.
Es soll aber auch gesagt sein, dass volatile an sich auch nicht so 
portabel ist wie das viele denken. Der C Standard macht nur sehr 
ungenaue Aussagen über die Funktion von volatile im Zusammenhang mit 
Daten.
Die meisten Compiler werden es aber als "schalte so gut wie alle 
Optimierungen für dieses Datum ab" übersetzen, aber Laufzeiteffekte 
(runtime reordering, SMP) ignorieren.

von Michael B. (mb_)


Lesenswert?

Peter Dannegger schrieb:
> Michael Buesch schrieb:
>> Außerdem gibts typeof, wie schon gesagt, mit dem man sehr tolle makros
>> machen kann, wenn man es denn unbedingt will.
>
> Danke, funktionert. Jetzt brauche ich nur noch ein Macro.
> typeof ist ja dann auch bequem, um Pointer anzulegen.

Nebenbei sei erwähnt, dass typeof das Makroargument nicht evaluiert. Es 
also keine Nebenwirkungen gibt. Also das wäre eine einmal-evaluation:

#define foo(bar)   typeof(bar) x = (bar)

Das wäre zweimal:

#define foo(bar)   typeof(bar) x = (bar) + (bar)

Je nachdem was "bar" ist und welche Nebenwirkungen es hat (Register oder 
komplexes statement), kann das natürlich fatal sein. Deshalb gibts diese 
Ausnahme für typeof.

>> Ein Beispiel würde mich sehr interessieren.
>
> Habe ich mir leider nicht gemerkt.
> Die Fehlermeldung könnte "lvalue required" gewesen sein.

Ja ok. Das kommt natürlich auch immer auf das Makro und die Verwendung 
an. Generell sollte es aber keine Probleme geben. Wenn man die Adresse 
des Makros (also des Makroinhalts) im Code nimmt, kann es zu Problemen 
kommen. Aber ich denke es wäre ohnehin ein Programmierfehler dies zu 
machen, weil diese Makros es ja eben verhindern wollen, dass der 
Anwender sich mit den Pointern befasst.

von Murkser (Gast)


Lesenswert?

Volatiles Are Miscompiled, and What to Do about It.
Eric Eide and John Regehr.
In Proceedings of the ACM Conference on Embedded Software (EMSOFT), 
Atlanta, GA, October 2008.

Hier das PDF: 
http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

Und hier ein Tool: http://www.cs.utah.edu/~eeide/emsoft08/

Murkser

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Michael Buesch schrieb:
> Volatile ansich ist eine ziemlich undefinierte Sache. Es ist quasi
> Compilerabhängig was wirklich passiert.

Das Verhalten des Compilers bei volatile Variablen ist meist im ABI
der Zielarchitektur definiert.

> Mit der Verwendung von barriers hat der compiler wesentlich mehr
> Möglichkeiten den Code zu optimieren.

Memory barriers ersetzen aber volatile nicht. Oft müssen sogar beide
Konstrukte gemeinsam genutzt werden.

Wie A.K. in Beitrag "Re: Wie man volatile optimiert" schon
am Rande andeutete, hat volatile einen Effekt, der meist übersehen
wird: Es verhindert eben auch, dass der Compiler un-optimiert. Bei
einer regulären Variablen ist es nicht festgelegt, dass der Compiler
den "besten" Speicherzugriff erzeugt. Er könnte einen 32bit Wert
problemlos durch vier einzelne Bytezugriffe laden. Genau das
verhindert volatile in den mir bekannten Fällen. Beispielsweise wird
gefordert, dass ein 32bit Wert mit genau einem 32bit breiten
Speicherzugriff geladen wird.

Das run-time reordering musst Du mit entsprechenden page-tables
verhindern, oder in Einzelfällen eben manuell durch memory barriers.

Gruß
Marcus

von Michael B. (mb_)


Lesenswert?

Marcus Harnisch schrieb:
> Memory barriers ersetzen aber volatile nicht. Oft müssen sogar beide
> Konstrukte gemeinsam genutzt werden.
>
> Wie A.K. in Beitrag "Re: Wie man volatile optimiert" schon
> am Rande andeutete, hat volatile einen Effekt, der meist übersehen
> wird: Es verhindert eben auch, dass der Compiler un-optimiert. Bei
> einer regulären Variablen ist es nicht festgelegt, dass der Compiler
> den "besten" Speicherzugriff erzeugt. Er könnte einen 32bit Wert
> problemlos durch vier einzelne Bytezugriffe laden. Genau das
> verhindert volatile in den mir bekannten Fällen. Beispielsweise wird
> gefordert, dass ein 32bit Wert mit genau einem 32bit breiten
> Speicherzugriff geladen wird.

Das wird nur bei memory mapped I/O gebraucht. Und genau dort (und 
eigentlich auch nur dort) ist volatile sinnvoll. Es wird aber auch 
vollständig von den I/O Wrapper macros verdeckt. Deshalb kann ich davon 
sprechen kein volatile in dem Code zu verwenden.

> Das run-time reordering musst Du mit entsprechenden page-tables
> verhindern, oder in Einzelfällen eben manuell durch memory barriers.

Was sollen page tables denn damit zu tun haben? MBs sind keine 
Einzelfälle, sondern die Regel um Reordering zu verhindern.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Michael Buesch schrieb:
> Was sollen page tables denn damit zu tun haben? MBs sind keine
> Einzelfälle, sondern die Regel um Reordering zu verhindern.

Einige Architekturen definieren mittels Page Tables sogenannte memory 
types (der Begriff kann sich natürlich je nach Hersteller 
unterscheiden). Diese memory types legen unter anderem fest, ob 
aufeinanderfolgende Zugriffe innerhalb der Page oder zwischen Pages 
verschiedenen Typs optimiert werden dürfen (reordering, store merging, 
etc). Siehe z.B. die ARMv6, bzw. ARMv7 VMSA.

Gruß
Marcus

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.