Wir wissen ja alle, Variablen sind in C bzw. C++ als volatile zu kennzeichnen, sobald sie sowohl im normalen Programmablauf als auch in einer ISR verwendet werden. Zu den Feinheiten der Interpretation gibt es hier ja auch etliche Threads. Nun bin ich in der Situation, dass ich volatile in einem Array theoretisch verwenden müsste. Ich würde es aber gern vermeiden, denn das Array ist groß und es werden sehr oft einzelne Elemente in verschiedenen Funktionen gelesen oder geändert. Nun würde in meinem Fall niemals jmd auf die Idee kommen, in einer ISR etwas am Array zu ändern. Hier wird brav nur gelesen. Also ist damit ja EIGENTLICH bereits ausgeschlossen, dass es während eines Zugriffs zu irgendeiner Unterbrechung kommt, nach der das Array anders aussieht. Gibt es Gründe, trzd als volatile zu deklarieren?
Ohne volatile ist nicht sicher, dass im C Quellcode geschriebene Daten auch wirklich zu diesem Zeitpunkt im Speicher stehen. Das kann sich beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht.
:
Bearbeitet durch User
Rolf Rolfus schrieb: > Wir wissen ja alle, Variablen sind in C bzw. C++ als volatile zu > kennzeichnen, sobald sie sowohl im normalen Programmablauf als auch in > einer ISR verwendet werden. Zu den Feinheiten der Interpretation gibt es > hier ja auch etliche Threads. > > Nun bin ich in der Situation, dass ich volatile in einem Array > theoretisch verwenden müsste. Ich würde es aber gern vermeiden, denn das > Array ist groß und es werden sehr oft einzelne Elemente in verschiedenen > Funktionen gelesen oder geändert. Nun würde in meinem Fall niemals jmd > auf die Idee kommen, in einer ISR etwas am Array zu ändern. Hier wird > brav nur gelesen. Also ist damit ja EIGENTLICH bereits ausgeschlossen, > dass es während eines Zugriffs zu irgendeiner Unterbrechung kommt, nach > der das Array anders aussieht. > > > Gibt es Gründe, trzd als volatile zu deklarieren? Wenn du mal scharf nachdenkst WARUM man die Variable als volatile Kennzeichnet wenn man sie im Programmablauf und der ISR verwendet, solltest du drauf kommen. Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array komplett wegzuoptimieren wenn es nur in der ISR gelesen wird?
Tim T. schrieb: > > Wenn du mal scharf nachdenkst WARUM man die Variable als volatile > Kennzeichnet wenn man sie im Programmablauf und der ISR verwendet, > solltest du drauf kommen. > Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array > komplett wegzuoptimieren wenn es nur in der ISR gelesen wird? Wenn du richtig lesen würdest, könntest du dir solche Antworten sparen. Es steht nirgends, dass das Array nur in ISRs gelesen wird. Da steht es wird in ISRs nur gelesen. Kleiner aber feiner Unterschied. :)
A. K. schrieb: > Ohne volatile ist nicht sicher, dass im C Quellcode geschriebene > Daten > auch wirklich zu diesem Zeitpunkt im Speicher stehen. Das kann sich > beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht. Sehr guter Punkt! Danke!
Tim T. schrieb: > Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array > komplett wegzuoptimieren wenn es nur in der ISR gelesen wird? Das ist der feine Unterschied zwischen "... wenn ISR nur liest" und "... wenn nur ISR liest"
Ohne volatile kannst du auch nach jedem Compilerlauf im Listing prüfen, ob die von dir gewünschte Funktionalität gegeben ist. Nervt etwas aber belohnt mit Performance.
my2ct schrieb: > Tim T. schrieb: >> Oder ums kurz zu machen, was hält z.B. den Compiler davon ab das Array >> komplett wegzuoptimieren wenn es nur in der ISR gelesen wird? > > Das ist der feine Unterschied zwischen "... wenn ISR nur liest" und > "... wenn nur ISR liest" Stimmt.
A. K. schrieb: > Das kann sich > beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht. Beliebig sicher nicht. Stichwort ist "Sequence point". Da eine ISR von der Natur der Sache nach asynchron ist, dürfte das also unerheblich sein. Die Wirkung auf das Programm, wenn das ganze Array volatile ist, scheint mir schlimmer. MfG Klaus
Rolf Rolfus schrieb: > Nun würde in meinem Fall niemals jmd > auf die Idee kommen, in einer ISR etwas am Array zu ändern. Hier wird > brav nur gelesen. So what? Die ISR unterbricht main zu jedem beliebigen Zeitpunkt. Wenn also main irgendwas am Datenbestand ändert, kann sieht die ISR eben keine konsistenten Datenstand mehr. Volatile hilft dagegen allerdings nicht. Nur eine Interruptsperre über alle Änderungen am Datenbestand hinweg, bis wieder ein in sich konsistenter Inhalt garantiert ist, den die ISR lesen darf. Sprich (vereinfacht): die Interruptsperre stellt sozusagen das Gegenstück zu volatile dar. Das eine garantiert die Datenkonsistenz bei der Datenrichtung main->ISR, das andere dasselbe für den umgekehrten Weg. Das Hauptproblem ist: der Programmierer muss festlegen, was ein "konsistenter Datenbestand" ist. Und über alle Verwicklungen in main hinweg sicherstellen, dass nie die ISR zuschlägt, wenn er halt nicht konsistent ist. Das führt in C leider oftmals zu unerträglich langen Interruptsperren, deren Maximaldauer obendrein noch schwer bis nicht vorhersagbar ist. Assembler rules...
c-hater schrieb: > … Wie du schon selbst gemerkt hast, ist das ein ganz anderes Thema. Hier geht es um volatile und nicht um atomaren Zugriff. Der ist sichergestellt...
Klaus schrieb: > Beliebig sicher nicht. Stichwort ist "Sequence point". Nein, ein sequence point hilft hier nicht weiter. Der garantiert nur, dass im bei Code a; b; c; in alle Änderungen in a auch in b und c sichtbar sind. Da der Compiler den Datenfluss in a,b,c kennt, darf er Daten in Registern behalten, die dem Quellcode nach eigentlich bereits in a im Speicher landen sollten. Er muss nur darauf achten, dass in b und c dann nicht auf den Speicher, sondern auf das Register zugegriffen wird. Eine ISR wird auf den Speicher zugreifen und die Altdaten sehen.
Klaus schrieb: > Beliebig sicher nicht. Stichwort ist "Sequence point". Da eine ISR von > der Natur der Sache nach asynchron ist, dürfte das also unerheblich > sein. Die Wirkung auf das Programm, wenn das ganze Array volatile ist, > scheint mir schlimmer. > > MfG Klaus Hmm, interessant. Aber diese Sequence Points zu bestimmen ist wieder so ein Thema für sich oder? Klingt auf den ersten Blick auch nicht gerade trivial.
Rolf Rolfus schrieb: > Hmm, interessant. Aber diese Sequence Points zu bestimmen ist wieder so > ein Thema für sich oder? Klingt auf den ersten Blick auch nicht gerade > trivial. Das ist relativ einfach, aber hier kaum relevant. In i++ + i++ ist unklar, was dabei rauskommt, weil kein sequence point dazwischen ist. In i++; i++; ist sichergestellt, dass der zweite Teil die aktualisierte Version vom ersten Teil sieht. Mit einer ISR hat das aber nichts zu tun, weil der ganze Code im Register ablaufen darf und die ISR solche Änderungen von i dann nicht mitbekommt.
A. K. schrieb: > Nein, ein sequence point hilft hier nicht weiter. Der garantiert nur, > dass im bei Code > a; b; c; > in alle Änderungen in a auch in b und c sichtbar sind. Hier geht es um ein Array und die ISR liest nur ein Element davon. Wenn ein Teil des Arrays nur in Registern aktuell ist, würde auch jede Funktion, die möglicherweise mit dem Array zu tun hat (was am Ende erst der Linker weiß), nicht funktionieren, unabhängig ob es eine ISR ist oder nicht. MfG Klaus
Klaus schrieb: > Hier geht es um ein Array und die ISR liest nur ein Element davon. Wenn > ein Teil des Arrays nur in Registern aktuell ist, würde auch jede > Funktion, die möglicherweise mit dem Array zu tun hat (was am Ende erst > der Linker weiß), nicht funktionieren, unabhängig ob es eine ISR ist > oder nicht. Der für den Compiler erkennbare Aufruf einer Funktion, deren Code der Compiler nicht kennt, sorgt dafür, dass er vor dem Aufruf alle "hängenden" Daten, die diese Funktion potentiell nutzen könnte, in den Speicher befördert. Der Unterschied zu einer ISR besteht darin, dass der Compiler keine Ahnung davon hat, dass überall im Code die ISR aufgerufen werden könnte. Folglich berücksichtigt er sie nicht.
:
Bearbeitet durch User
Gibt es in C keine "Optimierungsgrenzen" (Englisch Memory Barrier)? D.h. dass der Compiler bspw. nicht ueber die Funktionen hinweg optimieren darf. Da es sich um eine globale Variable handelt waere so sichergestellt, dass die Variable zeitnah geschrieben wird. Trotzdem kann der Compiler lokale Optimierungen durchfuehren. Das nur mal als Denkanstoss, kenne mich mit C nicht aus.
A. K. schrieb: > Der Unterschied zu einer ISR besteht darin, dass der Compiler keine > Ahnung davon hat, dass überall im Code die ISR aufgerufen werden könnte. > Folglich berücksichtigt er sie nicht. Das ist schon klar. Es geht aber um A. K. schrieb: > Das kann sich beliebig verzögern, das beliebig hier. Da die ISR nur ein Element aus dem Array liest, ist höchstens die Verzögerung ein Problem. MfG Klaus
Klaus schrieb: > Das ist schon klar. Es geht aber um > > A. K. schrieb: >> Das kann sich beliebig verzögern, > > das beliebig hier. Da die ISR nur ein Element aus dem Array liest, ist > höchstens die Verzögerung ein Problem.
1 | static int a[2]; |
2 | |
3 | int main () |
4 | {
|
5 | for (;;) |
6 | {
|
7 | if (PINB) |
8 | {
|
9 | a[0] = 1; |
10 | }
|
11 | |
12 | if (a[0]) |
13 | {
|
14 | tu_was_anderes (); |
15 | a[0] = 0; |
16 | }
|
17 | }
|
18 | }
|
Was hindert den Compiler daran, a[0] komplett über die ganze Laufzeit in
einem Register zu halten? Dass bei einem Array, dessen Größe über die
Anzahl der frei verfügbaren Register hinausgeht, das anders aussehen
kann, macht trotzdem die allgemeine Aussage von A.K.
> Das kann sich beliebig verzögern,
nicht falsch.
:
Bearbeitet durch Moderator
Frank M. schrieb: > Was hindert den Compiler daran, a[0] komplett über die ganze Laufzeit in > einem Register zu halten? Dass bei einem Array, dessen Größe über die > Anzahl der frei verfügbaren Register hinausgeht, das anders aussehen > kann, macht trotzdem die allgemeine Aussage von A.K. > >> Das kann sich beliebig verzögern, > > nicht falsch. Rolf Rolfus schrieb: > Ich würde es aber gern vermeiden, denn das Array ist *groß* Aber ok, der spitzfindigere hat gewonnen. MfG Klaus
Okay vielen Dank! Dann wird volatile also auf jeden Fall gesetzt und ich versuche das Drumherum dafür zu optimieren.
Rolf Rolfus schrieb: > Okay vielen Dank! Dann wird volatile also auf jeden Fall gesetzt > und ich versuche das Drumherum dafür zu optimieren. Das typische Pattern dafür geht über Schattenvariablen. Also Du ziehst Dir aus dem Array einen Eintrag in eine lokale Variable, die nicht volatile ist, arbeitest damit, und am Ende schreibst Du das wieder zurück ins Array. Und zwar sowohl im Hauptprogramm als auch in der ISR.
Zum ausprobieren: Definiere ein Array für eine 7 Seg. Displayanzeige die mit Multiplex arbeitet. Ändere unzyklisch in der Main die Daten im Array... Ohne volatile wird die nur lesende ISR ziemlichen Schrott auf das Display pinseln.
Klaus schrieb: > A. K. schrieb: >> Das kann sich >> beliebig verzögern, so dass die ISR im Speicher noch die Altdaten sieht. > > Beliebig sicher nicht. Doch. > Stichwort ist "Sequence point". Die haben damit erstmal nichts zu tun.
1 | x = 3; |
2 | x = 5; |
3 | x = 7; |
Der Compiler kann von den obigen drei Zeilen die ersten beiden wegoptimieren und die dritte beliebig weit nach hinten schieben, ggf. auch komplett wegoptimieren und beim nächsten Lesevorgang einfach die 7 direkt als Konstante einfügen. Und das, obwohl da drei Sequenzpunkte drin sind. Sequenzpunkte wirken sich nur auf die abstrakte Maschine aus, nicht zwingend auf den RAM des physischen Systems. > Da eine ISR von der Natur der Sache nach asynchron ist, dürfte das also > unerheblich sein. Die Wirkung auf das Programm, wenn das ganze Array > volatile ist, scheint mir schlimmer. Ein langsames Programm ist für mich weniger schlimm als ein nicht funktionierendes Programm. c-hater schrieb: > Volatile hilft dagegen allerdings nicht. Kleine Ergänzung: volatile alleine hilft dagegen nicht. Es ist notwendig, aber nicht hinreichend. Klaus schrieb: > Frank M. schrieb: >> Was hindert den Compiler daran, a[0] komplett über die ganze Laufzeit in >> einem Register zu halten? Dass bei einem Array, dessen Größe über die >> Anzahl der frei verfügbaren Register hinausgeht, das anders aussehen >> kann, macht trotzdem die allgemeine Aussage von A.K. >> >>> Das kann sich beliebig verzögern, >> >> nicht falsch. > > Rolf Rolfus schrieb: >> Ich würde es aber gern vermeiden, denn das Array ist *groß* > > Aber ok, der spitzfindigere hat gewonnen. Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem Register halten und nie in den Speicher rausschreiben.
Rolf Rolfus schrieb: > Gibt es Gründe, trzd als volatile zu deklarieren? Das volatile ist für den Interrupt uninteressant, sondern nur für das Main wichtig. Es sagt dem Main, daß der Zugriff erfolgen muß, d.h. auch ein Schreibzugriff. Man kann auch nur für das Main über ein Macro nach volatile casten, so daß der Interrupt seine Zugriffe weiterhin optimieren kann.
1 | #define IVAR(x) (*(volatile typeof(x)*)&(x))
|
Peter D. schrieb: > Rolf Rolfus schrieb: >> Gibt es Gründe, trzd als volatile zu deklarieren? > > Das volatile ist für den Interrupt uninteressant, sondern nur für das > Main wichtig. Es ist unwahrscheinlich, dass der Compiler den Zugriff in der ISR wegoptimiert, aber vom Prinzip her ist volatile dort genauso nötig wie in main.
Rolf M. schrieb: > Es ist unwahrscheinlich, dass der Compiler den Zugriff in der ISR > wegoptimiert, aber vom Prinzip her ist volatile dort genauso nötig wie > in main. Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim Verlassen speichern, also auch ein Interrupt. Nur die Mainloop kann es wegoptimieren, da sie ja nie verlassen wird. Einmalig aufgerufene oder kurze Unterfunktionen werden in Regel vom Optimizer geinlined, gehören also quasi mit zur Mainloop. Ein Sonderfall ist möglich, wenn 2 Interrupts unterschiedlicher Priorität die selbe Variable zugreifen. Dann muß der Interrupt niedrigerer Priorität auch volatile kapseln, damit er die Änderungen mitkriegt, wenn er selber unterbrochen wird.
Rolf M. schrieb: > Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code > auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem > Register halten und nie in den Speicher rausschreiben. Ich versuche mir gerade vorzustellen, wie z.B. ein strlen() auf einem Chararray abläuft, wenn ein Teil der Chars in Registern liegt. Aber du wirst mir das gleich erklären (ich ahne: ein Program daß nur aus main() und ISR besteht) MfG Klaus
Peter D. schrieb: > Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim > Verlassen speichern, also auch ein Interrupt. Den Verdacht hatte ich auch schon mal. Aber kann man das irgendwo nachlesen, bzw. geht das aus irgendeiner Formulierung im Standard zweifelsfrei hervor?
> Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim > Verlassen speichern, also auch ein Interrupt. Ja, die ISR lies den Wert aus dem Speicher, aber der ist veraltet, weil im Hauptprogramm der Wert nur im Register gehalten wird. Und genau da hat man ein Problem.
Bastler schrieb: > Und genau da hat man ein Problem. Lies Dir einfach nochmal meine beiden Beiträge durch, darin ist alles erklärt.
Klaus schrieb: > Ich versuche mir gerade vorzustellen, wie z.B. ein strlen() auf einem > Chararray abläuft, wenn ein Teil der Chars in Registern liegt. Die Funktion erhält als Parameter den Pointer auf das Array. Damit wird dem Compiler klar, dass das Array vollständig im Speicher liegen muss. Wobei die üblichen Aliasing-Regeln noch weitere Folgen haben, zumal ein char* recht heftige Folgen für solche Optimierungen hat. NB: Weshalb hältst Du C Compiler konsequent für strohdumm? Die wissen zwar nicht, was Du tust, aber oft ganz gut, was sie selber tun. Das haben Sie dem Menschen voraus, die haben nicht selten Probleme mit beidem. ;-)
:
Bearbeitet durch User
A. K. schrieb: > NB: Weshalb hältst Du C Compiler konsequent für strohdumm? Die wissen > zwar nicht, was Du tust, aber oft ganz gut, was sie selber tun. Das > haben Sie dem Menschen voraus, die haben nicht selten Probleme mit > beidem. ;-) Sorry, die Idee, daß ein Element eines Arrays nie im RAM sondern nur im Register lebt, stammt nicht von mir sondern von hier Rolf M. schrieb: > Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code > auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem > Register halten und nie in den Speicher rausschreiben. Bastler schrieb: > Ja, die ISR lies den Wert aus dem Speicher, aber der ist veraltet, weil > im Hauptprogramm der Wert nur im Register gehalten wird. > Und genau da hat man ein Problem. Ich hab nur versucht zu klären, wieviel Realität in dem immer bzw nur steckt. MfG Klaus
Rolf Rolfus schrieb: > > Nun bin ich in der Situation, dass ich volatile in einem Array > theoretisch verwenden müsste. Ich würde es aber gern vermeiden, denn das > Array ist groß und es werden sehr oft einzelne Elemente in verschiedenen > Funktionen gelesen oder geändert. Wenn Du immer nur ein einzelnes Element liest, einen geänderten Wert berechnest, und dann wieder wegschreibst, dann tut Dir das volatile auch nicht weh. Du solltest dann nur darauf achten, Zwischenschritte bei der Berechnung in lokalen Variablen zu machen. Wenn z.B. koordiniert mehrere Werte geschrieben und dann durch Setzen eines Flags als gültig markiert werden, helfen Speicherbarrieren (memory barrier), damit das Flag wirklich nach den Datenwerten geschrieben wird (bzw. dann vor ihnen gelesen wird). Beim Schreiben oder Lesen der Daten kann der Compiler dann trotzdem optimieren und dabei z.B. die Reihenfolge ändern.
Klaus schrieb: >> NB: Weshalb hältst Du C Compiler konsequent für strohdumm? Die wissen >> zwar nicht, was Du tust, aber oft ganz gut, was sie selber tun. Das >> haben Sie dem Menschen voraus, die haben nicht selten Probleme mit >> beidem. ;-) > > Sorry, die Idee, daß ein Element eines Arrays nie im RAM sondern nur im > Register lebt, stammt nicht von mir sondern von hier Und auch von mir. Wenn der Compiler sich entschliesst, Daten in Registern zu parken, dann weil er aus seiner Sicht weiss, dass dies keine Probleme verursacht. Wenn der Compiler allerdings sieht, dass von Daten die Adresse genommen wird, dann ists mehr oder weniger Essig mit dieser Art von Optimierung und er lässt es bleiben. Deshalb ja das Postulat, dass der Compiler weiss, was er tut. > Ich hab nur versucht zu klären, wieviel Realität in dem immer bzw > nur steckt. Wenn es deinem Seelenfrieden dient: "Ohne volatile ist nicht sicher, dass im C Quellcode geschriebene Daten auch wirklich zu diesem Zeitpunkt im Speicher stehen. Das kann sich um unbestimmte Zeit verzögern, so dass die ISR im Speicher noch die Altdaten sieht."
:
Bearbeitet durch User
A. K. schrieb: > Das kann sich um unbestimmte Zeit verzögern, so dass die ISR im Speicher > noch die Altdaten sieht." Die ISR, da asynchron, liest immer alte Daten. Und die unbestimmte Zeit ist auf jeden Fall zuende, wenn A. K. schrieb: > Wenn der Compiler allerdings sieht, dass von Daten die Adresse genommen wird oder eine Funktion aufgerufen wird, in der das globale Array möglicherweise angefasst wird. Damit ist ja alles klar. MfG Klaus
Klaus schrieb: > Die ISR, da asynchron, liest immer alte Daten. Und die unbestimmte Zeit > ist auf jeden Fall zuende, wenn > > Damit ist ja alles klar. Ich fürchte, dem ist nicht so. Denn auch dann ist bei int a[2]; f(a); a[i] = 1; ...2 Sekunden Beschäftigung... a[i] = 2; nicht sicher, dass 1 überhaupt geschrieben wird, wenn der Compiler weiss, dass zwischendrin nichts mit a oder Pointern angestellt wird. Er weiss zwar, dass auf a irgendwo zugegriffen werden könnte, weiss aber, dass es nicht zwischen 1 und 2 geschieht. Optimierung kann abschnittsweise völlig unterschiedlich ablaufen. Aber in int a[2]; f(a); a[i] = 1; g(); a[i] = 2; muss er vor Aufruf von g() die 1 durchschreiben, wenn er den Code von g() nicht kennt. Es kann sein, dass der in f(a) übergebene Pointer über dunkle Kanäle nach g() gelangt und dort genutzt wird.
:
Bearbeitet durch User
A. K. schrieb: > Es kann sein, dass der in f(a) übergebene Pointer über > dunkle Kanäle nach g() gelangt und dort genutzt wird. Zu meinem Verständnis: könntest du das näher erläutern? Wenn a[] und i nicht global sind und auch nicht in der Parameterliste von g() auftauchen, welche dunklen Kanäle siehst du da noch?
HildeK schrieb: > A. K. schrieb: >> Es kann sein, dass der in f(a) übergebene Pointer über >> dunkle Kanäle nach g() gelangt und dort genutzt wird. > > Zu meinem Verständnis: könntest du das näher erläutern? > Wenn a[] und i nicht global sind und auch nicht in der Parameterliste > von g() auftauchen, welche dunklen Kanäle siehst du da noch? int *p; void f(int *ap) { p = ap; } void g(void) { printf("%d", p[1]); }
:
Bearbeitet durch User
A. K. schrieb: > Ich fürchte, dem ist nicht so. Denn auch dann ist bei > int a[2]; > f(a); > a[i] = 1; > ...2 Sekunden Beschäftigung... > a[i] = 2; Dazu muß der Compiler aber alle Funktionen, die in den 2 Sekunden aufgerufen werden, komplett kennen um sicher zu wissen, das a[i] nie geändert wird. Ich verstehe schon, was du meinst, halte es aber trotzdem für eine Spitzfindigkeit, die für den TO ohne Bedeutung ist. Rolf Rolfus schrieb: > und es werden sehr oft einzelne Elemente in verschiedenen > Funktionen gelesen oder geändert. Und wenn dabei das i von a[i] auch noch zur Laufzeit berechnet wird, bleibt einem Compiler, der fehlerfreien Code erzeugen will, nichts übrig als ins reale Array zu schreiben. MfG Klaus
Peter D. schrieb: > Rolf M. schrieb: >> Es ist unwahrscheinlich, dass der Compiler den Zugriff in der ISR >> wegoptimiert, aber vom Prinzip her ist volatile dort genauso nötig wie >> in main. > > Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim > Verlassen speichern, also auch ein Interrupt. Mir wäre keine Regel bekannt, die sowas vorgibt. Klaus schrieb: > Rolf M. schrieb: >> Um noch etwas spitzfindiger zu sein: Der Compiler könnte in dem Code >> auch das Array im Speicher anlegen, aber a[0] dennoch immer in einem >> Register halten und nie in den Speicher rausschreiben. > > Ich versuche mir gerade vorzustellen, wie z.B. ein strlen() auf einem > Chararray abläuft, wenn ein Teil der Chars in Registern liegt. Aber du > wirst mir das gleich erklären (ich ahne: ein Program daß nur aus main() > und ISR besteht) Wenn da noch ein strlen()-Aufruf dazwischen ist, wird der Optimizer das natürlich berücksichtigen. Klaus schrieb: > Ich hab nur versucht zu klären, wieviel Realität in dem immer bzw > nur steckt. Das "immer" bezog sich genau auf das obige Progamm, nicht auf jedes beliebige. Wenn man das Programm ändert, ist es natürlich möglich, dass sich auch das Verhalten ändert. Wenn ein Programm eine Schleife enthält, in der von einem Array immer das selbe Element je Durchlauf einmal gelesen und einmal geschrieben wird und sonst nichts mit ihr angestellt wird, ist es in diesem spezifischen Programm unnötig, jedesmal den Wert aus dem Speicher in ein Register zu lesen und später wieder aus dem Register zurück in den Speicher zu schreiben. Das kann man weglassen, ohne dass sich irgendwas ändert, und genau nach solchen Situationen sucht der Optimizer. Wenn du aber eine ISR hast, die außerhalb des regulären Programmablaufs plötzlich auch zugreifen will, funktioniert das nicht. Und genau dann braucht man volatile, um dem Compiler zu sagen, dass er die Speicherzugriffe auf jeden Fall durchführen muss.
Klaus schrieb: > Dazu muß der Compiler aber alle Funktionen, die in den 2 Sekunden > aufgerufen werden, komplett kennen um sicher zu wissen, das a[i] nie > geändert wird. Bei LTO kennt er alle Funktionen komplett.
Klaus schrieb: > Ich versuche mir gerade vorzustellen, wie z.B. ein > strlen() auf einem Chararray abläuft, wenn ein Teil > der Chars in Registern liegt. Es steht dem Compiler frei, strlen() zu kennen und das Ergebnis vorwegzunehmen oder dessen Berechnung auszurollen. Für Funktionen wie memcpy oder memset ist das jedenfalls durchaus üblich.
Im Falle von gcc sind ziemlich viele Standardfunktionen bereits im Compiler selbst eingebaut. Siehe https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
Eine Möglichkeit, den Speicherinhalt zu "invalidieren" ist eine Memory-Barrier; ein GCC-Feature. GNU-C Beispiel:
1 | char array[10]; |
2 | |
3 | void func (void) |
4 | {
|
5 | array[1] = 2; |
6 | array[1] = 3; |
7 | __asm__ __volatile__ ("" ::: "memory"); |
8 | array[1] = 4; |
9 | array[1] = 5; |
10 | }
|
Optimierend übersetzt mit avr-gcc:
1 | func: |
2 | ldi r30,lo8(array) |
3 | ldi r31,hi8(array) |
4 | ldi r24,lo8(3) |
5 | std Z+1,r24 |
6 | ldi r24,lo8(5) |
7 | std Z+1,r24 |
8 | /* epilogue start */ |
9 | ret |
Hier werden nur die Werte 3 und 5 gespeichert. Die 3, weil eine Memory-Barrier folgt und die 5, weil GCC nicht weiß, was nach func() folgt. 2 und 4 werden nicht gespeichert, weil sie durch 3 bzw. 5 überschrieben werden. Falls die Memory-Barrier nicht per Gießkanne erfolgen soll sondern nur bzgl. array, kann dies per asm-Argument modelliert werden:
1 | __asm__ __volatile__ ("" : "+m" (array)); |
Genau genommen ist die Operation nicht volatile, allerdings will man i.d.R. vermeiden, dass sie mit einer volatile Anweisung kommutiert. (Analog will man i.d.R. auch, dass eine volatile-Anweisung wie ein NOP nicht mit einem Speicherzugriff kommutiert. Dies ist der Grund, warum Makros / Lib-Funktionen für NOP etc. ein memory-Clobber enthalten sollten.) Weiterhin ist die Frage zu beantworten, warum Code mit volatile array schlechter optimiert als ohne volatile. Operationen direkt auf der volatile-Variable sollte dann vermieden werden und stattdessen mit einer lokalen Kopie gearbeitet werden falls möglich. Beispiel:
1 | char volatile array[10]; |
2 | |
3 | void func_1 (void) |
4 | {
|
5 | array[0]++; |
6 | if (array[0] > 10) |
7 | array[0] = 0; |
8 | }
|
9 | |
10 | void func_2 (void) |
11 | {
|
12 | char a = array[0] + 1; |
13 | if (a > 10) |
14 | a = 0; |
15 | array[0] = a; |
16 | }
|
func_2 gibt nicht nur besseren Code: func_1 leidet an einem möglichen Glitch: Eine ISR findet u.U. den Wert 11 in array[0] vor, was den Code in func_1 weiter verkompliziert / verlangsamt, falls dies zu vermeiden ist:
1 | #include <util/atomic.h> |
2 | |
3 | void func_1 (void) |
4 | {
|
5 | ATOMIC_BLOCK (ATOMIC_RESTORESTATE) |
6 | {
|
7 | array[0]++; |
8 | if (array[0] > 10) |
9 | array[0] = 0; |
10 | }
|
11 | }
|
Zur Referenz hier noch das Compilat, wieder mit avr-gcc:
1 | func_1: |
2 | in r25,__SREG__ |
3 | /* #APP */ |
4 | cli |
5 | /* #NOAPP */ |
6 | lds r24,array |
7 | subi r24,lo8(-(1)) |
8 | sts array,r24 |
9 | lds r24,array |
10 | cpi r24,lo8(11) |
11 | brlt .L2 |
12 | sts array,__zero_reg__ |
13 | .L2: |
14 | out __SREG__,r25 |
15 | ret |
16 | |
17 | func_2: |
18 | lds r24,array |
19 | subi r24,lo8(-(1)) |
20 | cpi r24,lo8(11) |
21 | brlt .L4 |
22 | ldi r24,0 |
23 | .L4: |
24 | sts array,r24 |
25 | ret |
func_1 ist nicht nur größer und langsamer, sondern hat auch eine größere Interrupt-Latenz. Obwohl eine Memory-Barrier eine Möglichkeit ist, würde ich selbst volatile bzw. Atomics falls verfügbar vorziehen.
Rolf M. schrieb: >> Nö, jede Funktion muß eine globale Variable beim Eintritt lesen und beim >> Verlassen speichern, also auch ein Interrupt. > > Mir wäre keine Regel bekannt, die sowas vorgibt. Ich denke, dazu bedarf es keiner Regel. Eine Funktion muß aus jedem Kontext gerufen werden können (auch rekursiv), sie weiß also nicht, ob eine globale Variable gerade in einem Register ist. Und an ihrem Ende muß sie damit rechnen, daß ihr gesamter Kontext (außer dem Returnwert) vernichtet wird. Johann L. schrieb: > und die 5, weil GCC nicht weiß, was nach func() folgt. Es bleibt also nichts anderes übrig, als am Anfang zu lesen und am Ende zu schreiben. MfG Klaus
Klaus schrieb: > Eine Funktion muß aus jedem Kontext gerufen werden können > (auch rekursiv), sie weiß also nicht, ob eine globale > Variable gerade in einem Register ist. So die Theorie. Wenn der Compiler weiß, dass eine Funktion niemals so aufgerufen wird, dann muss er keinen Code generieren, der damit umgehen kann. Er muss nichtmal die Funktion selbst generieren. Klaus schrieb: >> Mir wäre keine Regel bekannt, die sowas vorgibt. > Ich denke, dazu bedarf es keiner Regel. Wenn es keine Regel gibt, die ein bestimmtes Verhalten vorschreibt, dann sollte man sich darauf nicht unbedingt verlassen. Du triffst Annahmen über den Compiler, die zwar meistens zutreffen, es aber nicht müssen.
:
Bearbeitet durch User
S. R. schrieb: > Wenn es keine Regel gibt, die ein bestimmtes Verhalten vorschreibt, dann > sollte man sich darauf nicht unbedingt verlassen. Du triffst Annahmen > über den Compiler, die zwar meistens zutreffen, es aber nicht müssen. Die globale Regel heißt, der Compiler muß immmer korekten Code liefern. Und die gilt immer, nicht nur meistens. Und nur für die, die das nicht verstanden haben, wird das nicht extra nochmal mal aufgeschrieben. MfG Klaus
S. R. schrieb: > Klaus schrieb: >>> Mir wäre keine Regel bekannt, die sowas vorgibt. >> Ich denke, dazu bedarf es keiner Regel. > > Wenn es keine Regel gibt, die ein bestimmtes Verhalten vorschreibt, dann > sollte man sich darauf nicht unbedingt verlassen. Du triffst Annahmen > über den Compiler, die zwar meistens zutreffen, es aber nicht müssen. Richtig, das ist der entscheidende Unterschied. Man kann entweder dem Sprachstandard folgen, laut dem das volatile nötig ist und es einfach hinschreiben. Oder man kann tief in den Codegenerierungsprozess einsteigen und sich überlegen, warum es höchstwahrscheinlich trotzdem funktioniert, auch wenn man das laut Standard nötige volatile weglässt. Mir erscheint Variante 1 sinnvoller. Klaus schrieb: > Die globale Regel heißt, der Compiler muß immmer korekten Code > liefern. Natürlich. Dabei darf er aber die Struktur des Programms berücksichtigen. Er muss nicht damit rechnen, dass etwas passiert, das gar nicht im Programm steht.
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.