Forum: Compiler & IDEs Geschwindigkeit bei volatile-variablen


von Der Hans (Gast)


Lesenswert?

Hi,

fange gerade an mit gcc. Jetzt habe ich schon gelernt, dass man 
Variablen, die man in ISR's ändern will, volatile deklarieren muss, 
damit sie nicht in Registerbänke wegoptimiert werden, sondern im SRAM 
liegen.

Das bereitet mir aber ein ungutes Gefühl, weil der Zugriff auf den SRAM 
ja langsamer ist als auf interne Register. Ich befürchte Probleme bei 
zeitkritischen Anwendungen.

Ist das berechtigt? Oder unproblematisch?

schöne Grüsse

Hans

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


Lesenswert?

Das ist aber eben genau das, was man für die Kommunkation zwischen
den beiden Threads (wenn man sie so nennen will) braucht.  Allerdings
sollte man die "volatile" deklarierte Variable wirklich nur für den
Datenaustausch nehmen.  Wenn man mit den Dingern mehr als eine einzelne
Abfrage/Zuweisung macht, sollte man sie besser in lokalen Variablen
cachen (die dann wieder optimiert werden können).

Klar kann man für wirklich zeitkritische Dinge auch noch reine
Registervariablen zwischen ISR und main() zur Kommunikation benutzen,
aber das ist eher ein Sonderfall.

von OliverSo (Gast)


Lesenswert?

Nun ja, wenn "volatile" erforderlich ist, ist es erforderlich. Schneller 
geht es nicht, denn ohne funktioniert das Programm nicht mehr so, wie es 
soll.

Aber ich würde mal sagen, du machst dir unbegründete Sorgen. Solltest du 
jemals (was ich nicht glaube) an eine Anwendung geraten, die so 
zeitkritisch ist, daß es auf jeden Prozessorzyklus ankommt, gibt es 
immer noch Assembler, oder einen schnelleren Quarz.

Oliver

von Philipp B. (philipp_burch)


Lesenswert?

OliverSo wrote:
> zeitkritisch ist, daß es auf jeden Prozessorzyklus ankommt, gibt es
> immer noch Assembler, oder einen schnelleren Quarz.

Die zweite Variante sollte man aber nur im alleräussersten Notfall in 
Betracht ziehen ;)

Ansonsten lohnt es sich bei einigermassen hochfrequent aufgerufenen ISRs 
das Disassembly zu prüfen. Dann sieht man auch, ob volatile Zugriffe ein 
Problem sein können oder nicht.

@Jörg:

Cachen ist eine gute Idee, danke.

von Rolf Magnus (Gast)


Lesenswert?

> Klar kann man für wirklich zeitkritische Dinge auch noch reine
> Registervariablen zwischen ISR und main() zur Kommunikation benutzen,
> aber das ist eher ein Sonderfall.

Die kann man allerdings leider nicht volatile machen.

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


Lesenswert?

Rolf Magnus wrote:
>> Klar kann man für wirklich zeitkritische Dinge auch noch reine
>> Registervariablen zwischen ISR und main() zur Kommunikation benutzen,
>> aber das ist eher ein Sonderfall.
>
> Die kann man allerdings leider nicht volatile machen.

Und mittlerweile gibt's nichtmal mehr mit -Wall eine Warnung dafür. :-(

von Uhu U. (uhu)


Lesenswert?

Jörg Wunsch wrote:
> Wenn man mit den Dingern mehr als eine einzelne
> Abfrage/Zuweisung macht, sollte man sie besser in lokalen Variablen
> cachen (die dann wieder optimiert werden können).

Ob das in dieser Allgemeinheit ein guter Ratschlag ist, möchte ich stark 
bezweifeln, denn dann definiert man die betreffende Variable besser 
nicht volatile.

volatile garantiert, daß jeder Lesezugriff auf die betreffende Variable 
immer ihren aktuellen Wert benutzt, nicht irgendeinen früheren. Es ist 
kein Ersatz für Synchronisationsmechanisman!

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


Lesenswert?

Uhu Uhuhu wrote:

> Ob das in dieser Allgemeinheit ein guter Ratschlag ist, möchte ich stark
> bezweifeln, denn dann definiert man die betreffende Variable besser
> nicht volatile.

Warum willst du die Variable zwingend als volatile behandeln, wenn
klar ist, dass sie im Moment von niemandem sonst angefasst werden
kann, bspw. innerhalb der ISR?  Trotzdem ist die Deklaration als
volatile der sinnvolle und richtige Weg.

von Uhu U. (uhu)


Lesenswert?

Jörg Wunsch wrote:
> Warum willst du die Variable zwingend als volatile behandeln, wenn
> klar ist, dass sie im Moment von niemandem sonst angefasst werden
> kann, bspw. innerhalb der ISR?

Das ist ein Spezialfall: Die Variable ist in einer ISR durch einen 
Synchronisationsmechanismus geschützt - die Interruptsperre.

Wenn man im Hauptprogramm entsprechend Ausdrücke, die auf die Variable 
zugreifen, ebenfalls unter Interruptsperre berechnet, dann braucht man 
kein volatile.

von Karl H. (kbuchegg)


Lesenswert?

Uhu Uhuhu wrote:
> Jörg Wunsch wrote:
>> Warum willst du die Variable zwingend als volatile behandeln, wenn
>> klar ist, dass sie im Moment von niemandem sonst angefasst werden
>> kann, bspw. innerhalb der ISR?
>
> Das ist ein Spezialfall: Die Variable ist in einer ISR durch einen
> Synchronisationsmechanismus geschützt - die Interruptsperre.

Ich glaube ihr redet aneinander vorbei.

Was Jörg ursprünglich angesprochen hatte, ist dieser Fall

volatile uint8_t Wert;

ISR( ... )
{
  Wert = 8 * Wert + 75;
  y = 23 * Wert;
  x = y * Wert / Wert;
}

int main()
{
  ...
  while( Wert ) {
     ...
  }
}

also einfach irgendwelche Berechnungen, in denen die volatile
Variable mehrfach vorkommt. (Obige Berechnungen sind sinnlos,
sollen einfach nur verdeutlichen, dass die volatile Variable
mehrfach vorkommt).

Da hier das volatile aber notwendig ist, ensteht die Situation
dass das voltaile innerhalb der ISR kontraproduktiv ist, weil
es den Compiler am Register Optimieren hindert.
Mit einer gecachten Version der Variable umugeht man das Problem

volatile uint8_t Wert;

ISR( ... )
{
  uint8_t WertCache;

  WertCache = Wert = 8 * Wert + 75;
  y = 23 * WertCache ;
  x = y * WertCache / WertCache ;
}

int main()
{
  ...
  while( Wert ) {
     ...
  }
}

>
> Wenn man im Hauptprogramm entsprechend Ausdrücke, die auf die Variable
> zugreifen, ebenfalls unter Interruptsperre berechnet, dann braucht man
> kein volatile.

Dieser Satz macht für mich keinen Sinn, weil volatile und 
Interruptsperre
ja verschiedene Problemstellungen lösen.

von Uhu U. (uhu)


Lesenswert?

Karl heinz Buchegger wrote:
>> Wenn man im Hauptprogramm entsprechend Ausdrücke, die auf die Variable
>> zugreifen, ebenfalls unter Interruptsperre berechnet, dann braucht man
>> kein volatile.
>
> Dieser Satz macht für mich keinen Sinn, weil volatile und
> Interruptsperre ja verschiedene Problemstellungen lösen.

Das ist richtig. volatile ist kein Synchronisationsmechanismus.

Aber: Wenn man durch Synchronisationsmechanismen sicherstellt, daß eine 
asynchron veränderliche Variable sich in den Abschnitten nicht verändern 
kann, in denen mit ihr gerechnet wird, dann braucht man sie nicht 
als volatile zu deklarieren.

Globale Interruptsperre ist so ein Synchronisationsmechanismus. Das 
Semaphor-Konzept ist ein anderer.

von Falk B. (falk)


Lesenswert?

@  Jörg Wunsch (dl8dtl)

>> Die kann man allerdings leider nicht volatile machen.

>Und mittlerweile gibt's nichtmal mehr mit -Wall eine Warnung dafür. :-(

Naja, aber mal im Ernst, wo wird denn WIRKLICH ne globale, volatile 
Variable im Register gebraucht? Mit register soll man AFAIK sowieso 
eher sparsam bis gar nicht umgehen, der Compiler ist (meist) schlau 
genug das selber hinzufummeln. Und für das Handshake zwischen Interrupt 
und Main ist eine volatile Variable im RAM allemal schnell genug.

MFG
Falk

von Benedikt K. (benedikt)


Lesenswert?

Uhu Uhuhu wrote:

> Aber: Wenn man durch Synchronisationsmechanismen sicherstellt, daß eine
> asynchron veränderliche Variable sich in den Abschnitten nicht verändern
> kann, in denen mit ihr gerechnet wird, dann braucht man sie /nicht/
> als volatile zu deklarieren.

Dumm ist nur, wenn der Compiler die Variable dann wegoptimiert, da er 
denkt, dass diese nirgends verändert wird.
Es funktioniert zwar ab und zu, aber es ist Pfusch und eine extreme 
Fehlerquelle. Lieber einmal volatile zuviel geschrieben, dafür das 
Programm um 1% ausgebremst, als 2 Stunden Fehlersuche.

von Uhu U. (uhu)


Lesenswert?

Benedikt K. wrote:
> Uhu Uhuhu wrote:
>
>> Aber: Wenn man durch Synchronisationsmechanismen sicherstellt, daß eine
>> asynchron veränderliche Variable sich in den Abschnitten nicht verändern
>> kann, in denen mit ihr gerechnet wird, dann braucht man sie
>> nicht als volatile zu deklarieren.
>
> Dumm ist nur, wenn der Compiler die Variable dann wegoptimiert, da er
> denkt, dass diese nirgends verändert wird.

Der Compiler optimiert deshalb mit Sicherheit keine Variable weg - ob er 
überhaupt Variablen wegoptimierten darf, möchte ich stark bezweifeln. Er 
kann zwar die Berechnung von Ausdrücken optimieren, aber nicht Daten.

> Es funktioniert zwar ab und zu, aber es ist Pfusch und eine extreme
> Fehlerquelle.

Mir scheint, du hast nicht begriffen, worum es sich handelt: Einzelne 
durch Synchronisationsmechanismen geschützte Programmabschitte, in 
denen sich die Variable nicht verändern kann. Das heißt noch lange 
nicht, daß sie sich außerhalb der geschützten Abschnitte nicht ändern 
kann - dann wurde sie in einem anderen geschützten Abschnitt 
geschrieben.

> Lieber einmal volatile zuviel geschrieben, dafür das Programm um 1%
> ausgebremst, als 2 Stunden Fehlersuche.

Wenn das mal so einfach wäre...

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


Lesenswert?

Falk Brunner wrote:

>>Und mittlerweile gibt's nichtmal mehr mit -Wall eine Warnung dafür. :-(

> Naja, aber mal im Ernst, wo wird denn WIRKLICH ne globale, volatile
> Variable im Register gebraucht?

Für genau den hier besprochenen Fall: Kommunikation zwischen ISR und
Hauptkontext, aus Geschwindigkeitsgründen über ein permanent dafür
belegtes Register.  Das spart der ISR nämlich das Retten eines
entsprechenden Registers, Auslesen und Rückschreiben des Speichers
und Rückspeichern des alten Registers.

> Mit register soll man AFAIK sowieso
> eher sparsam bis gar nicht umgehen, ...

Wir reden nicht über das praktisch obsolete Schlüsselwort "register"
allein, sondern über sowas:
1
register uint8_t some_ISR_state asm("r2");

von Andreas S. (andreas) (Admin) Benutzerseite


Lesenswert?

Uhu Uhuhu wrote:
> Aber: Wenn man durch Synchronisationsmechanismen sicherstellt, daß eine
> asynchron veränderliche Variable sich in den Abschnitten nicht verändern
> kann, in denen mit ihr gerechnet wird, dann braucht man sie /nicht/
> als volatile zu deklarieren.

Eine Variable auf die von verschiedenen "Threads" bzw. 
Mainloop/Interrupt zugegriffen wird muss immer volatile deklariert 
sein, völlig unabhängig von Synchronisationsmechanismen. Sonst würde zum 
Beispiel folgendes nicht funktionieren:
1
interrupt bla {
2
  x = 1;
3
}
4
5
...
6
7
x = 0;
8
while(x == 0); // warten auf Ereignis

Wie Jörg schon geschrieben hat sind volatile und Semaphoren o.ä. 
Lösungen für zwei verschiedene Probleme, das eine lässt sich nicht durch 
das andere ersetzen.

von Karl H. (kbuchegg)


Lesenswert?

Uhu Uhuhu wrote:
> Benedikt K. wrote:
>> Uhu Uhuhu wrote:
>>
>>> Aber: Wenn man durch Synchronisationsmechanismen sicherstellt, daß eine
>>> asynchron veränderliche Variable sich in den Abschnitten nicht verändern
>>> kann, in denen mit ihr gerechnet wird, dann braucht man sie
>>> nicht als volatile zu deklarieren.
>>
>> Dumm ist nur, wenn der Compiler die Variable dann wegoptimiert, da er
>> denkt, dass diese nirgends verändert wird.
>
> Der Compiler optimiert deshalb mit Sicherheit keine Variable weg - ob er
> überhaupt Variablen wegoptimierten darf, möchte ich stark bezweifeln. Er
> kann zwar die Berechnung von Ausdrücken optimieren, aber nicht Daten.
>

Benedikt hat einen lesenden Zugriff auf eine Variable gemeint.

Mir scheint du sprichst von einem ganz andern Problem-Fall als
dem um den es Jörg ursprünglich ging.

Schau dir noch mal die Programm-Skizze an, die ich weiter oben
gepostet habe. Das volatile ist dort notwendig und wichtig.
Das Dumme ist nur, dass genau dieses volatile innerhalb der
ISR den Optimizer in seiner Arbeit behindert.

von Benedikt K. (benedikt)


Lesenswert?

Karl heinz Buchegger wrote:

> Benedikt hat einen lesenden Zugriff auf eine Variable gemeint.

Ja, so ist es. Das Beispiel hat Andreas ja schon geliefert. Ohne 
volatile wird x wegoptimiert, und die Warte Schleife durch eine 
Endlosschleife ersetzt.

von Uhu U. (uhu)


Lesenswert?

Andreas Schwarz wrote:
> Sonst würde zum Beispiel folgendes nicht funktionieren:
>
>
1
> interrupt bla {
2
>   x = 1;
3
> }
4
> 
5
> ...
6
> 
7
> x = 0;
8
> while(x == 0); // warten auf Ereignis
9
>

Das Argument sticht.

von Uhu U. (uhu)


Lesenswert?

Benedikt K. wrote:
> Karl heinz Buchegger wrote:
>
>> Benedikt hat einen lesenden Zugriff auf eine Variable gemeint.
>
> Ja, so ist es. Das Beispiel hat Andreas ja schon geliefert. Ohne
> volatile wird x wegoptimiert, und die Warte Schleife durch eine
> Endlosschleife ersetzt.

Nein, nicht x wird wegoptimiert, sondern 'while (!x)' wird gegen
'while (1)' ausgetauscht. Die Variable x existiert trotzdem weiter.

von Karl H. (kbuchegg)


Lesenswert?

Uhu Uhuhu wrote:
> Benedikt K. wrote:
>> Karl heinz Buchegger wrote:
>>
>>> Benedikt hat einen lesenden Zugriff auf eine Variable gemeint.
>>
>> Ja, so ist es. Das Beispiel hat Andreas ja schon geliefert. Ohne
>> volatile wird x wegoptimiert, und die Warte Schleife durch eine
>> Endlosschleife ersetzt.
>
> Nein, nicht x wird wegoptimiert, sondern 'while (!x)' wird gegen
> 'while (1)' ausgetauscht. Die Variable x existiert trotzdem weiter.


Lass uns jetzt bitte nicht Haare spalten.
Genauso hat es Benedikt gemeint.

von Uhu U. (uhu)


Lesenswert?

Das Effizienzproblem kriegt man auch so aus der Welt:
1
union {
2
   volatile uint8_t volWert;
3
   unint8_t         Wert;
4
} W;
5
6
ISR( ... )
7
{
8
  W.Wert = 8 * W.Wert + 75;
9
  y = 23 * W.Wert;
10
  x = y * W.Wert / W.Wert;
11
}
12
13
int main()
14
{
15
  ...
16
  while( W.volWert ) {
17
     ...
18
  }
19
}

Ersatzweise wäre auch diese Definition möglich:
1
static union {
2
   volatile uint8_t volWert;
3
   unint8_t         Wert;
4
};
5
6
ISR( ... )
7
{
8
  Wert = 8 * Wert + 75;
9
  y = 23 * Wert;
10
  x = y * Wert / Wert;
11
}
12
13
int main()
14
{
15
  ...
16
  while( volWert ) {
17
     ...
18
  }
19
}

von Rolf Magnus (Gast)


Lesenswert?

Das ist genauso Pfusch. volatile

>> Dumm ist nur, wenn der Compiler die Variable dann wegoptimiert, da er
>> denkt, dass diese nirgends verändert wird.
>
> Der Compiler optimiert deshalb mit Sicherheit keine Variable weg - ob
> er überhaupt Variablen wegoptimierten darf, möchte ich stark
> bezweifeln.

Er darf alles wegoptimieren, was sich nicht auf I/O oder 
volatile-Zugriffe auswirkt. Wenn eine Variable nicht volatile ist, darf 
sie auch komplett wegoptimiert werden. Bei globalen Variablen wird der 
Compiler das aber vermutlich nie tun, außer wenn sie static deklariert 
sind.

> Er kann zwar die Berechnung von Ausdrücken optimieren, aber nicht
> Daten.

Doch, kann er. Bei lokalen Variablen passiert das auch oft.

> Mir scheint, du hast nicht begriffen, worum es sich handelt: Einzelne
> durch Synchronisationsmechanismen geschützte Programmabschitte, in
> denen sich die Variable nicht verändern kann. Das heißt noch lange
> nicht, daß sie sich außerhalb der geschützten Abschnitte nicht ändern
> kann - dann wurde sie in einem anderen geschützten Abschnitt
> geschrieben.

Daß sie wirklich geschrieben und gelesen wird, kann man aber nur mit 
volatile erzwingen, denn genau dazu ist volatile da.

von Peter D. (peda)


Lesenswert?

Ich hoffe ja, daß nun endlich klar ist, daß volatile und atomar 2 völlig 
verschiedene Dinge sind, die auch manchmal zusammen benötigt werden.


Zurück zum Thema volatile:

Jörg hatte mal eine sehr elegante Methode gepostet, indem man die 
Variable im Main castet, um den Zugriff zu erzwingen. Damit kann der 
Interrupt voll optimieren.

Geht bloß nicht direkt, sondern über die Brust ins Auge per Pointer.
Hier mal ein Beispiel:
1
#include <interrupt.h>
2
3
unsigned char eval;
4
5
#define vu8(x)  (*(volatile unsigned char*)&(x))
6
#define vs8(x)  (*(volatile signed char*)&(x))
7
#define vu16(x) (*(volatile unsigned short*)&(x))
8
#define vs16(x) (*(volatile signed short*)&(x))
9
#define vu32(x) (*(volatile unsigned long*)&(x))
10
#define vs32(x) (*(volatile signed long*)&(x))
11
12
13
void test( void )
14
{
15
  eval = 1;
16
17
  while( eval );        // never ending story part I
18
}
19
20
21
void test1( void )
22
{
23
  unsigned char i;
24
25
  eval = 1;
26
27
  do{
28
    cli();
29
    i = eval;           // atomar but not volatile
30
    sei();
31
  }while( i );          // never ending story part II
32
}
33
34
35
void test2( void )
36
{
37
  eval = 1;
38
39
  while( vu8(eval) );   // finished by interrupt
40
}


Peter

von Uhu U. (uhu)


Lesenswert?


von Karl H. (kbuchegg)


Lesenswert?

Uhu Uhuhu wrote:
> Das geht auch ohne cast:
> Beitrag "Re: Geschwindigkeit bei volatile-variablen"

<Haare spalten>
Hat aber den Vorteil, dass es sich hierbei um Standard-C handelt. :-)
</Haare spalten>

von Benedikt K. (benedikt)


Lesenswert?

@Peter
Was bringt das atomar in dem Beispiel ?  Ein Bytes aus dem SRAM lesen 
ist ein Takt, da kann doch nichts schiefgehen.
Bei einer 16 oder 32bit Zahl würden auch die Makros nichts bringen, ohne 
zusätzliches cli/sei.

von Sebastian (Gast)


Lesenswert?

>Wir reden nicht über das praktisch obsolete Schlüsselwort "register"
>allein, sondern über sowas:

>register uint8_t some_ISR_state asm("r2");

Frage an den Jörg, ist das wirklich so einfach ?
Wenn die Variable dann wirklich im r2 gehalten wird, dann wäre das 
wirklich genial und man könnte locker auf volatile verzichten.

Das muß ich mal ausprobieren

Gruß Sebastian

von Peter D. (peda)


Lesenswert?

Benedikt K. wrote:
> @Peter
> Was bringt das atomar in dem Beispiel ?  Ein Bytes aus dem SRAM lesen
> ist ein Takt, da kann doch nichts schiefgehen.

Das bringt nur den Beweis, daß volatile und atomar nichts miteinander zu 
tun haben.
Ich habs einzig und allein wegen der obigen Diskussionen mit 
reingestellt.


> Bei einer 16 oder 32bit Zahl würden auch die Makros nichts bringen, ohne
> zusätzliches cli/sei.

Volatile erzwingt kein atomar und umgekehrt.
Man muß, wenn nötig, beides verwenden.


Peter

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


Lesenswert?

Sebastian wrote:

>>register uint8_t some_ISR_state asm("r2");

> Frage an den Jörg, ist das wirklich so einfach?

Antwort vom Sender Jerewan: im Prinzip ja.  Allerdings müssen
natürlich alle translation units dieselbe Deklaration dafür
sehen, einschließlich der translation units, die die (System-)
Bibliothek bilden.

> Wenn die Variable dann wirklich im r2 gehalten wird, dann wäre das
> wirklich genial und man könnte locker auf volatile verzichten.

Nein, kann man nicht.  Wenn man im nicht-ISR-Kontext eine Abfrage
à la
1
   while (!some_ISR_state)
2
      /* wait */ ;

hat, kann der Compiler ohne volatile wieder schlussfolgern, dass
some_ISR_state sich gar nicht mehr ändern kann, und die Abfrage
auf r2 nur einmal einbauen.  Nur funktioniert eben volatile register
im GCC dummerweise nicht.

von Sebastian (Gast)


Lesenswert?

Hallo Jörg,
danke für die Antwort.
Das mit verzicht auf volatile war etwas unüberlegt von mir,
natürlich kann man das nicht weglassen.
>Nur funktioniert eben volatile register
>im GCC dummerweise nicht.

Schade, sonst könnte man die 'unteren' Register sinvoll nutzen

Gruß Sebastian

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.