std::queue auf dem ESP32

Gast #7313511
Lesenswert?

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?

#7313526
Lesenswert?

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.

Gast #7313549
Lesenswert?

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.

Gast #7314016
Lesenswert?

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/

Gast #7314793
Lesenswert?

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?

Persönliche Seite #7314797
Lesenswert?

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

Gast #7314805
Lesenswert?

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.

Gast #7314808
Lesenswert?

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.

#7314822
Lesenswert?

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?

Gast #7318087
Lesenswert?

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.

#7318429
Lesenswert?

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
Gast #7322629
Lesenswert?

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.

Gast #7322687
Lesenswert?

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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren