Forum: Mikrocontroller und Digitale Elektronik std::queue auf dem ESP32


von Guntre (Gast)


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?

von Wilhelm M. (wimalopaan)


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.

von Guntre (Gast)


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.

von Steve van de Grens (roehrmond)


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/

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Guntre (Gast)


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?

von Niklas G. (erlkoenig) Benutzerseite


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

von Dr. Swan (Gast)


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.

von Guntre (Gast)


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.

von Εrnst B. (ernst)


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?

von grünunterdernase (Gast)


Lesenswert?

du kannst pool allocator aus der boost lib nehmen

https://stackoverflow.com/a/1304166

von Mario (Gast)


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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Klar, safety auf dem ESP32 :D

von Vincent H. (vinci)


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

: Bearbeitet durch User
von gtz (Gast)


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.

von avr (Gast)


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.

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.