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.
Gibt es noch andere eindrucksvolle Beispiele wo über volatile -Variable optimiert werden kann? Was ist mit dem ganzen SIMD-Zeugs?
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.
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.
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
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...
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
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?
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.
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.
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
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.
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
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).
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
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.
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.
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 | }
|
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ß.
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
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.
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.
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
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
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.