ich lerne gerade C++ mithilfe eines ESP32 Projekts, keine nennenswerten C-Kenntnisse, daher Anfängerfrage: In meinem Projekt habe ich eine std::queue um einen asynchronen event bus (oder command queue wenn man will) zu realisieren. Innerhalb von interrupts wird was reingeschrieben und in der main loop wird dann am anderen Ende ausgelesen und abgearbeitet. Jetzt habe ich gelesen, dass solche Datenstrukturen, wie auch z.B. vector, heap allocation machen. Ich geh jetzt zwar nicht davon aus, dass die queue gefährlich groß wird, aber ich habe in dem Zusammenhang auch von heap fragmentation gelesen, die sich über die Zeit verschlimmern kann. Da die queue ständig beschrieben und ausgelesen wird, kann ich mir vorstellen (weiß es aber nicht genau), dass da viel Allokation und Freigabe passiert die ganze Zeit und daher fragmentiert (falls ich das mit der Fragmentierung jetzt richtig verstehe). Insgesamt finde ich widersprüchliche Infos dazu, Hinweise, dass man heap allocation im embedded gar nicht machen soll, andere, die sagen, das sei quatsch, wenn man die Größe der collections voraussagen kann. Es scheint auch die Möglichkeit zu geben, die Allokationsstrategie auf diverse Arten zu beeinflussen. Da blick ich aber noch nicht so durch. Könnt ihr mir ein paar Tipps zur Orientierung geben oder klarstellen, was ich da verzapfe?
Guntre schrieb: > ich lerne gerade C++ mithilfe eines ESP32 Projekts, Kein gute Idee > In meinem Projekt habe ich eine std::queue um einen asynchronen event > bus (oder command queue wenn man will) zu realisieren. Innerhalb von > interrupts wird was reingeschrieben und in der main loop wird dann am > anderen Ende ausgelesen und abgearbeitet. Achtung: in C++ sind konflikt-behaftete Zugriffe auf eine gemeinsame Datenstruktur undefined behaviour, wenn keine geeigneten Maßnahmen ergriffen werden. Die Realisierung von std::queue ist sicher nicht lock-free/atomic, also musst Du die happens-before Relation einhalten. Bei Nebenläufigkeit via Threads könnte man das mit mutex/cv herstellen, bei Interrupts geht es über Interruptsperren und memory-barriern.
Wilhelm M. schrieb: > Guntre schrieb: >> ich lerne gerade C++ mithilfe eines ESP32 Projekts, > > Kein gute Idee das mit den guten Ideen hab ich vor Jahren aufgegeben. Sonst würde ich hier nicht posten ;) > >> In meinem Projekt habe ich eine std::queue um einen asynchronen event >> bus (oder command queue wenn man will) zu realisieren. Innerhalb von >> interrupts wird was reingeschrieben und in der main loop wird dann am >> anderen Ende ausgelesen und abgearbeitet. > > Achtung: in C++ sind konflikt-behaftete Zugriffe auf eine gemeinsame > Datenstruktur undefined behaviour, wenn keine geeigneten Maßnahmen > ergriffen werden. Die Realisierung von std::queue ist sicher nicht > lock-free/atomic, also musst Du die happens-before Relation einhalten. > Bei Nebenläufigkeit via Threads könnte man das mit mutex/cv herstellen, > bei Interrupts geht es über Interruptsperren und memory-barriern. sprichst du darauf an, dass ggf verschiedene Interrupts konfliktbehaftet auf die Datenstruktur zugreifen? Aus reiner Anwendungssicht ist Nebenläufigkeit und "korrekte" Synchronisation nicht so kritisch (glaube ich aktuell). Ich benutze das nur um die interrupts und einen asynchronen Webserver (der crasht, wenn ich blockierendes Zeugs direkt aus dessen handlern aufrufe) zu entkoppeln und sich das mit queues sehr bequem gestaltet.
Guntre schrieb: > Insgesamt finde ich widersprüchliche Infos dazu, Hinweise, dass man heap > allocation im embedded gar nicht machen soll, andere, die sagen, das sei > quatsch, wenn man die Größe der collections voraussagen kann. Dynamische Speicherverwaltung scheitert manchmal an der Fragmentierung des Heaps. Ob dein Programm von dem Problem betroffen ist, hängt sehr stark davon ab, in welcher Reihenfolge bein Programm unterschiedlich große Blöcke belegt und wieder frei gibt. Der Effekt ist hier erklärt: https://www.mikrocontroller.net/articles/Heap-Fragmentierung Und da gibt es noch einen schönen Aufsatz zum Thema: https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/
Das ESP-IDF hat dafür einen extra RingBuffer: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos_additions.html#ring-buffers Ist auch thread-safe und super für sowas wie Command-Queues geeignet.
Niklas G. schrieb: > Das ESP-IDF hat dafür einen extra RingBuffer: > > https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/freertos_additions.html#ring-buffers > > Ist auch thread-safe und super für sowas wie Command-Queues geeignet. Klingt gut. Könnte ich damit unter Arduino auch arbeiten, wenn ich FreeRTOS einbinde oder ist das speziell nur unter ESP-IDF verfügbar?
Guntre schrieb: > Klingt gut. Könnte ich damit unter Arduino auch arbeiten, wenn ich > FreeRTOS einbinde oder ist das speziell nur unter ESP-IDF verfügbar? Anscheinend basiert der Arduino-ESP32-Core sowieso auf dem ESP-IDF, d.h. FreeRTOS ist aktiv und alle anderen IDF-Funktionen sind direkt verfügbar: https://medium.com/home-wireless/how-to-program-an-esp32-in-arduino-while-using-esp-idf-functions-90033d860f75
Es ist richtig, dass std::queue und andere C++ Standard-Datenstrukturen, wie std::vector, Heap-Speicherallokation verwenden, um ihre Elemente zu speichern. Heap-Fragmentierung kann tatsächlich zu einem Problem werden, wenn es um Embedded-Systeme geht, da der verfügbare Heap-Speicher begrenzt sein kann und durch häufige Allokation und Freigabe von Speicherplatz fragmentiert werden kann. In Bezug auf Ihr Projekt, da die std::queue ständig beschrieben und ausgelesen wird, ist es wahrscheinlich, dass es zu einer Menge Heap-Allokation und Freigabe kommt. Eine Möglichkeit, dies zu vermeiden, ist die Verwendung einer Ringpuffer-Datenstruktur anstelle einer std::queue. Ein Ringpuffer verwendet einen festen Speicherbereich und kann daher die Heap-Fragmentierung vermeiden. Eine andere Möglichkeit besteht darin, die Verwendung von std::queue mit Vorsicht zu verwenden und sicherzustellen, dass die maximale Größe der Queue begrenzt wird, um die Heap-Fragmentierung zu minimieren. Es gibt auch Möglichkeiten, die Allokationsstrategie in C++ zu beeinflussen, wie z.B. durch die Verwendung von benutzerdefinierten Allokatoren. Aber das ist ein fortgeschrittenes Thema und es ist empfehlenswert, sich erstmal mit den Grundlagen auseinandersetzen. Letztendlich hängt es von den Anforderungen Ihres Projekts und der verfügbaren Ressourcen ab, welche Technik am besten geeignet ist. Es ist wichtig, die Auswirkungen auf die Speicherverwaltung und die Leistung zu berücksichtigen, bevor Sie eine Entscheidung treffen.
Steve van de Grens schrieb: > Dynamische Speicherverwaltung scheitert manchmal an der Fragmentierung > des Heaps. Ob dein Programm von dem Problem betroffen ist, hängt sehr > stark davon ab, in welcher Reihenfolge bein Programm unterschiedlich > große Blöcke belegt und wieder frei gibt. > > Der Effekt ist hier erklärt: > https://www.mikrocontroller.net/articles/Heap-Fragmentierung > Und da gibt es noch einen schönen Aufsatz zum Thema: > https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/ Danke. Dass Strings das gleiche Problem haben, hatte ich gar nicht auf dem Schirm.
Guntre schrieb: > um einen asynchronen event > bus (oder command queue wenn man will) zu realisieren Nur so als Denkanstoß: Schau dir die Datenstruktur (Binary-)Heap / priority queue an. Damit kannst du deine Events/Tasks priorisieren, und die ganze Struktur lässt sich auf ein Array (ggfs. mit fixer Größe) mappen, dann bist du alle dynamischen Speicheranforderungen los. Nachteil: Einfügen&Entnehmen eines Events sind dann O(n log n). Solange du nicht tausende Events verwalten willst, ist das aber relativ egal. Apropos dynamische Speicherverwaltung: ist malloc&co sicher aus der ISR verwendbar, oder knallt das wenn der IRQ grad einen malloc-Aufruf aus der main unterbricht?
Mikrocontroller und Boostlib: jetzt wird's richtig lustig. Wenn Dein Projekt privat ist, OK, probiere alles aus, was es gibt. Sollte Dein Projekt irgendwelche Sicherheitsbelange (Safety) erfordern? !!! DANN FINGER WEG VON DYNAMISCHEN SPEICHER - OPERATIONEN !!! Dann müssen alle Variablen statisch angelegt werden.
Guntre schrieb: > Insgesamt finde ich widersprüchliche Infos dazu, Hinweise, dass man heap > allocation im embedded gar nicht machen soll, andere, die sagen, das sei > quatsch, wenn man die Größe der collections voraussagen kann. Es scheint > auch die Möglichkeit zu geben, die Allokationsstrategie auf diverse > Arten zu beeinflussen. Da blick ich aber noch nicht so durch. Diese Pauschalaussagen kommen aus Zeiten wo Mikrocontroller keine 8MB SRAM hatten. Dass das bei einem ESP32 wohl nicht mehr so allgemein gilt macht wohl folgendes deutlich:
1 | ╭─vinci@garuda in ~/esp-idf took 2ms |
2 | ╰─λ grep -r "malloc" | wc -l |
3 | 3741
|
:
Bearbeitet durch User
Vincent H. schrieb: > Diese Pauschalaussagen kommen aus Zeiten wo Mikrocontroller keine 8MB > SRAM hatten. > > Dass das bei einem ESP32 wohl nicht mehr so allgemein gilt macht wohl > folgendes deutlich: >
1 | > ╭─vinci@garuda in ~/esp-idf took 2ms |
2 | > ╰─λ grep -r "malloc" | wc -l |
3 | > 3741 |
4 | >
|
3741 mallocs im Quellcode sagen ja noch nichts darüber, wie häufig diese aufgerufen werden, unter welchen Bedingungen. Der OT spricht von einer dynamischen queue die ständig in Bewegung ist. Schwer zu sagen zu was für einer Fragmentierung das führt. Aber du hast Recht, schwarzweiß Pauschalisierung ist wenig hilfreich.
Mario schrieb: > Sollte Dein Projekt irgendwelche Sicherheitsbelange (Safety) erfordern? > !!! DANN FINGER WEG VON DYNAMISCHEN SPEICHER - OPERATIONEN !!! > Dann müssen alle Variablen statisch angelegt werden. Bullshit. Safety bedeutet, dass solche Fehlerfälle erkannt werden und rechtzeitig in den sicheren Zustand gewechselt wird. Das schließt dynamische Speicherverwaltung keinesfalls aus.
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.