Du bist nicht angemeldet.

Werbeanzeige

1

23.12.2019, 15:32

C++ | volatile Klasseninstanz [gelöst]

Hallo Leute,

ich arbeite zurzeit an einem Projekt auf einem Mikrocontroller (ATSAM3x8e / ARM-Cortex M3 Architektur) in C++ und probiere aus, wie gut sich modernes C++ auf Low-Level verwenden lässt.

Dabei bin ich auf ein großes Problem gestoßen:
Ich habe eine globale Klasseninstanz, auf die von mehreren Interrupts und aus dem Hauptprogramm schreibend und lesend zugegriffen werden kann. Der atomare Zugriff auf diese Instanz ist durch den Afubau des Programms gewährleistet, jedoch muss sie trotzdem volatile sein. Genauer gesagt, geht es um selbst implementierte 2D und 3D Vektroklassen. Das große Problem welches ich dabei habe, sind die Operatoren (+, -, *, /, +=, -=, *=, /=, =, ==).
Zurzeit habe ich das ganze noch, als Header-Only implementiert, da ich mir so einen Geschwindigkeitsvorteil durchs inlinen erhoffe. Hier einmal mein jetziger Stand:


Am Ende solles möglich sein, folgenden Code (und weiteare erdenkliche Kombinationen) ohne Fehler compilieren und ausführen zu können:

C-/C++-Quelltext

1
2
3
4
volatile Vec2f vec1;
Vec2f vec2 = vec1;
Vec2f vec3(vec1 + Vec2f(1.0f, 2.0f));
vec1 += vec3 * 7.0f;


Bei meiner jetzigen Variante scheitert es daran, dass vec1 += 'irgend ein rvalue' nicht funktioniert, da es keine volatile rvalue Referenzen gibt.

Mit vielen weiteren überladenen Operatoren würde man zwar auch dieses Problem lösen können, aber gibt es da nicht einen simpleren Weg. Oder sollte ich meine Programmstruktur noch einmal überdenken?
Albert Einstein sagte: "2 Stunden mit einem netten Mädchen fühlen sich an wie 20 Minuten, 20 Minuten auf einem heißen Ofen fühlen sich an wie 2 Stunden. - Das ist Relativität"

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Patrick Z.« (29.12.2019, 18:45)


Schrompf

Alter Hase

Beiträge: 1 357

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

2

23.12.2019, 16:32

Da volatile eh deprecated ist und verschwinden wird, wäre mein Vorschlag: lass es sein. Dann hast Du ne Standard-Vektorklasse, die sich genau so benutzen lassen sollte, wie Du es wünschst. Das volatile würde ich mindestens eine Ebene höher ansetzen, also die Instanz der Vektorklasse volatile machen, nicht jeden Member davon.

Stilkritik:
  • wenn Du ne class gleich mit nem public: eröffnest, dann sei ehrlich zu Dir und Deinen Kollegen und mach ne struct draus.
  • das if(other == self) im Zuweisungsoperator solltest Du löschen. Das bringt in ner Klasse mit simplen Membervars nix, sieht sehr nach Lehrstuhl-Gewohnheit aus.
  • allgemein ist ne Vektorklassee ein Prachtexemplar der Rule Of Zero. Wenn Du eh nix anderes tust als jede Membervar einzeln zu kopieren, kannst Du das auch komplett dem Compiler überlassen.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

3

23.12.2019, 16:46

Danke für die schnelle Rückmeldung.

Das volatile kann ich nicht weglassen, da es einfach bei Mikrocontrollern und Interrupt Routinen nicht anders geht (außer ich hab irgendwas komplett verpast), also muss ich eine Klasse haben, von der ich auch eine verwendbare volatile Instanz haben kann. Ursprünglich habe ich eh nur die Instanz als volatile deklariert, aber von einer volatile Instanz kann man keine non-volatile Memberfunktionen aufrufen (wie bei const), also musste ich alle Operatoren volatile machen, und ab da haben die ganzen Probleme angefangen (z.B: Wie kann ich ein non-volatile rvalue zu einer volatile Instanz addieren?)

Ich habs bis jetzt eigentlich immer so gehandhabt, das alles was Memberfunktionen (außer Operatoren, Konstruktoren und Destruktoren) hat, eine Klasse ist (weil ichs von C noch gewohnt bin, dass Structs keine Memberfunktionen haben). Wenn es standardmäßig so gemacht wird, wie du es geschrieben hast, bin ich dir dankbar für den Hinweis.

Das if(other == self) hab ich tatsächlich stumpf aus diversen Tutorials abgekuckt :hmm:

Die Rule Of Zero kenne ich, aber es werden keine volatile Assignment Operatoren automatisch erstellt (dachte ich halt), die ich aber brauche, wenn ich eine volatile Instanz haben möchte. Oder?
Albert Einstein sagte: "2 Stunden mit einem netten Mädchen fühlen sich an wie 20 Minuten, 20 Minuten auf einem heißen Ofen fühlen sich an wie 2 Stunden. - Das ist Relativität"

Nox

Supermoderator

Beiträge: 5 282

Beruf: Student

  • Private Nachricht senden

4

24.12.2019, 01:16

Der Umstand, dass du mit Instanzen einer Vektorklasse in einem Interrupt hantieren willst, macht mich stutzig. Eine wichtige Erkenntnis, die ich mit viel Lehrgeld zahlen musste, ist dass Interruptroutinen möglichst knapp gehalten werden sollten. Sprich großen Berechnungen (und dazu würde ich lineare Algebra zählen) vermeiden und lieber ein "compute" Flag oder ein paar Regs setzen aber nicht mehr. Dann kann man im Hauptprogramm auf entsprechende Änderungen checken und ggf die Berechnung durchführen. Warum? Bei vielen µCs werden alle anderen Interrupts deaktiviert solange man einen Interrupt abarbeitet. Das kann zu hässlichen Ergebnissen bis hin zu einem Totalabsturz führen, wenn man wichtige Events dadurch verpasst (z.B. voll laufenden Buffer). Zwar kann man bei einigen die Interruptbehandlung auch innerhalb der Interruptroutine wieder aktivieren, aber dann kann es schnell zu hässlichen Verschachtelungen kommen, was wiederum zu schwer kontrollierbaren Verhalten führen kann. KA in wiefern diese Aussagen auch auf einen ARM M3 zutreffen, aber darauf anlegen würde ich es nicht.

TL;DR my 2 cents: Berechnungen nicht in die Interruptroutine packen.
PRO Lernkurs "Wie benutze ich eine Doku richtig"!
CONTRA lasst mal die anderen machen!
networklibbenc - Netzwerklibs im Vergleich | syncsys - Netzwerk lib (MMO-ready) | Schleichfahrt Remake | Firegalaxy | Sammelsurium rund um FPGA&Co.

5

24.12.2019, 12:56

Ich muss die Berechnungen leider in der ISR ausführen, da es sich dabei um eine recht zeitempfindliche Regelung handelt, welche relativ genau mit einer gewissen Frequenz arbeiten muss (hier 100Hz). Deshalb auch das inline, da ich mir dadurch bessere Performance erhoffe (ja, ich weiß, dass das den Compiler nicht wirklich beeinflusst, ob er es jetzt wirklich inlined oder nicht). Man kann beim ARM M3 sehr genau bestimmen, welcher Interrupt welchen anderen unterbrechen darf. Dadurch schaffe ich es ja auch, dass cimmer ein "atomarer" Zugriff stattfindet (bei direktem Lesen / Schreiben auf die Variable kann kein anderer ISR ausgelöst werden). Das mit dem Flag geht auch aus einem anderen Grund nicht, da das Probramm (es soll am Ende ein Roboter sein) in der Hauptschleife manchmal Tasks ausführt, die viel zu lange dauern, die aber durch Interrupts unterbrochen werden können. Daher ist die ganze Regelungs- und Messtechnik direkt in den ISR verpackt.
Ich habe auch schon einmal die Zeit gemessen, die der ATSAM3x8e für diese ISR braucht, und die liegt bei ca. 50uS, also ist es kein Problem, dass die Berechnungen direkt in der ISR durchgeführt werden.

Ich habe inzwischen das Problem mit volatile gelöst:
Das letzte Hindernis, war, dass es in C++ nicht möglich ist eine volatile Refernz auf ein rvalue zu haben. Um mir eine Überladung pro Operator zu sparen bräuchte ich diese aber:
Bsp:

C-/C++-Quelltext

1
2
3
4
volatile Vec2f vec1(0.0f, 0.0f);
volatile Vec2f vec2(1.0f, 1.0f);
vec1 += Vec2f(1.0f, 1.0f);   // Ruft "const volatile Vec2f& Vec2f::operator=(const Vec2f& vec) volatile" auf
vec1 += vec2;                // Ruft "const Vec2f& Vec2f::operator=(const volatile Vec2f& vec) volatile" auf

Nachdem aber normalerweise implizi ein cv-type qualifier hinzugefügt werden kann, müsste der Operator, der ein const volatile Vec2f& entgegennimmt ausreichen. Da es aber keine volatile Referenzen auf rvalues gibt (macht eigentlich auch Sinn), funktioniert die Zeile 3 nicht mehr, wenn ich nur den einen Opertaor überlade. Wenn ich beide Operatoren überlade, wäre folgender Code ambiguous:

C-/C++-Quelltext

1
2
3
volatile Vec2f vec1(0.0f, 0.0f);
Vec2f vec2(1.0f, 1.0f);
vec1 += vec2;


Meine Lösung für dieses Problem ist jetzt: Alle nötigen Operatoren einmal komplett ohne volatile definieren, und dann bei allen volatile Operatoren die volatile Argumente nicht per Referenz sondern per Value übergeben. So sind alle möglichen Kombinationen abgedeckt (zwar nicht alle optimal), und wenn man normal mit den Vektoren arbeitet (ohne volatile), werden die besser optimierten non-volatile Operatoren verwendet.

Hier der funktionierende Code:


Falls jemand weitere Fehler oder Grundlegende Fehler in der Programmsturktur findet, darf sie mir mitteilen. :)
Albert Einstein sagte: "2 Stunden mit einem netten Mädchen fühlen sich an wie 20 Minuten, 20 Minuten auf einem heißen Ofen fühlen sich an wie 2 Stunden. - Das ist Relativität"

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Patrick Z.« (24.12.2019, 13:24)


David Scherfgen

Administrator

Beiträge: 10 237

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

6

24.12.2019, 16:41

Reicht es nicht global einen (volatile) Zeiger auf einen Vektor zu haben, den du dann atomar änderst? Könnte auch ein Index in ein 2-elementiges Vektor-Array sein, quasi wie ein double buffer. Das würde alles viel einfacher machen.

7

24.12.2019, 18:03

Die Idee ist zwar gut, geht aber auch nicht, da sobald ich den volatile Zeiger dereferenziere, ist es eine volatile Instsnz, von der ich nur volatile Memberfunktionen aufrufen kann. Und ohne volatile Zeiger ist der Compiler erlaubt auch das dereferenzierte zu cachen. Das tut er auch (getestet mit gcc-arm 6.irgendwas mit Optimierung O2). Und den Zeiger mit const_cast zu vorm lesen/schreiben zu einem non-volatile Zeiger machen ist undefined behaviour.

Ps: Frohe Weihnachten
Albert Einstein sagte: "2 Stunden mit einem netten Mädchen fühlen sich an wie 20 Minuten, 20 Minuten auf einem heißen Ofen fühlen sich an wie 2 Stunden. - Das ist Relativität"

David Scherfgen

Administrator

Beiträge: 10 237

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

8

24.12.2019, 19:00

Geht es mit einem volatile Index in ein zwei-elementiges Array (double buffer)? Wahrscheinlich auch nicht.

Frohe Weihnachten!

9

25.12.2019, 12:09

Ich habs ausprobiert, mit O2 funktoiniert es, aber bei O3 wird die Funktion, in der der atomare Zugriff auf den double buffer stattfindet geinlined, und der Wert wird über mehrere Aufrufe hinweg nicht erneut geladen. Nachdem ich mir nicht sicher bin, ob wir den Code am Ende in O3 kompilieren, würde ich das nicht so machen. Ich bleib inzwischen bei meiner Lösung mit den vielen Operatoren :D
Albert Einstein sagte: "2 Stunden mit einem netten Mädchen fühlen sich an wie 20 Minuten, 20 Minuten auf einem heißen Ofen fühlen sich an wie 2 Stunden. - Das ist Relativität"

Schrompf

Alter Hase

Beiträge: 1 357

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

10

28.12.2019, 20:58

Klingt evtl. so, als willst Du ein template <typename Scalar> struct Vec2 { };, dass Du dann mit std::atomic_float instanziierst.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

Werbeanzeige