Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

1

26.09.2017, 21:06

C++ Lambdafunktionen: Wo wird der Programmcode gespeichert?

Hey Leute,

ich melde mich mal wieder zu Wort! Seit 1,5 Jahren beschäftige ich mich nun mit AtMegas und AtTinys. Ist eine schöne und interessante Sache, da man plötzlich genau auf seinen Speicherverbrauch achten muss.

Ein Programm von mir, das auf einem AtMega 2560 läuft braucht in der Main-Loop konstant ca. 96% RAM. Das Programm läuft schon 3 Monate durch und bis jetzt verändert sich der RAM-Verbrauch nicht. Dennoch würde ich ihn gerne etwas reduzieren. In meinem Programm mache ich regen Gebrauch von kleinen (1-5 Zeilen) Lambdafunktionen. Denn ich habe mir ein Eventsystem programmiert, das Lambdafunktionen als Callbacks akzeptiert. Das ist für mich möglich einfach und schnell Events mit Code zu verknüpfen. Viele Lambdafunktionen kann ich aber auch als Memberfunktionen programmieren. Im Internet habe ich gelesen, dass eine Lambdafunktion als Objekt gehandhabt wird. Meine Frage: Wo wird der Programmcode gespeichert? Im Flash oder wird der wie strings (ohne F()) komplett im RAM gelagert? Ändert sich dieses Verhalten, wenn ich Captures verwende oder keine? std::function verwende ich nicht, da ich die Standardbibliothek generell nicht brauche, die frisst mir zu viel Speicher... stattdessen werden die Lambdas mit Templates gespeichert. Falls das interessiert schicke ich auch gerne mal den Code mit.

Schonmal danke für eure Antworten!
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

2

26.09.2017, 21:15

Der Code von Lambda-Funktionen wird natürlich nicht im RAM abgelegt, sondern ganz normal wie jede andere Funktion auch kompiliert, landet also in der Code-Sektion (bei dir: Flash-Speicher). Womit misst du denn den Speicherverbrauch? Forderst du dynamisch Speicher an? Wird von der Speicherverwaltung vielleicht der gesamte RAM als "Pool" für Allokierungen genutzt?

PS: Nur mal aus Interesse - wie hattest du dir denn vorgestellt, dass der Code als String gespeichert wird? Als Klartext? Dann müsste der ja zur Laufzeit interpretiert/kompiliert werden, d.h. da müsste ein kompletter C++-Compiler mit integriert werden. Ein bisschen unrealistisch, oder? ;)

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

3

26.09.2017, 21:55

Nur mal generell: lambda expressions sind per Definition nichts anderes als Kurzschreibweise für das Deklarieren und Instanzieren einer entsprechenden Klasse mit einem überladenen operator() und den entsprechend initialisierten Captures als Datenmembern. Für den resultierenden Maschinencode sollte es im Allgemeinen keinen Unterschied machen ob du Lambdas verwendest oder per Hand entsprechende Klassen schreibst...

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

4

28.09.2017, 00:42

Womit misst du denn den Speicherverbrauch?
Hiermit Der Code erstellt einen Zeiger, der auf den ersten leeren Platz im Heap zeigt und "läuft" mit diesem dann durch den freien Heap. Das Ende wird erkannt, wenn kein Zeiger mehr vor dem Alten erstellt werden kann.

Forderst du dynamisch Speicher an?
Ja, ziemlich oft, da mein Programm eine Konfiguration von der SD Karte lädt und sich dann dynamisch zusammenbaut. Dies passiert aber nur beim Start, während der Laufzeit wird kein weiterer dynamischer Speicher angefordert. Freigegeben wird generell nichts, da nur Objekte erzeugt werden, die für immer gebraucht werden. Ich verwende so viel es geht statische Allokierungen, da ich aber viel mit polymorphen Klassen arbeite muss ich oft dynamischen Speicher verwenden... :(

PS: Nur mal aus Interesse - wie hattest du dir denn vorgestellt, dass der Code als String gespeichert wird? Als Klartext? Dann müsste der ja zur Laufzeit interpretiert/kompiliert werden, d.h. da müsste ein kompletter C++-Compiler mit integriert werden. Ein bisschen unrealistisch, oder? ;)
Meinte nicht, dass der Code als String gespeichert wird. :P Ich meinte, dass das gleiche Spiechermanagement angewandt wird wie für Strings: Der Code wird wie alle Stringliterale auch im Flash gespeichert. Aber die Literale werden zum Programmstart im RAM angelegt und verweilen dort die ganze Zeit über. Vielleicht wird der Code beim Laden als Maschinencode auch im RAM gespeichert. Das ist nicht schlau und wie ich jetzt weiß wirds auch nicht so gemacht, aber man weiß ja nie... Hab schon zu merkwürdige Dinge erlebt. ;)

Wird von der Speicherverwaltung vielleicht der gesamte RAM als "Pool" für Allokierungen genutzt?
Der Speicher wird wie in diesem Modell beschrieben verwaltet. Würde deine Frage also mit Ja beantworten.

Nur mal generell: lambda expressions sind per Definition nichts anderes als Kurzschreibweise für das Deklarieren und Instanzieren einer entsprechenden Klasse mit einem überladenen operator() und den entsprechend initialisierten Captures als Datenmembern. Für den resultierenden Maschinencode sollte es im Allgemeinen keinen Unterschied machen ob du Lambdas verwendest oder per Hand entsprechende Klassen schreibst...
Danke, das bringt eine Menge Klarheit! :)

Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

5

28.09.2017, 08:54

Eine der beiden Methoden wird für jeden Ausgangspin, den es in meinen Programmen gibt einmal aufgerufen. Das sind bei besagtem Programm 50 Stück. Lambdas sind anonyme Klassen, wird dann bei jedem Aufruf eine neue Klassendefinition erstellt oder nur ein neues Objekt erstellt? Also die Klassendefinition wiederverwendet? Sorgt das für den hohen Speicherverbrauch?

Eine Klassendefinition kann nicht "bei jedem Aufruf" erstellt werden. Klassen können nur zur Compile-Zeit definiert werden, nicht zur Laufzeit. Außerdem braucht die reine Definition einer Klasse keinen Heap-Speicher. Ja, es werden Objekte (auf dem Stack) erzeugt, aber auch anschließend wieder gelöscht. Schau mal hier, da wird ganz gut erklärt, was der Compiler tatsächlich aus deiner Lambda-Funktion macht: https://blog.feabhas.com/2014/03/demystifying-c-lambdas/
Kurzgefasst: Deine Lambda-Funktionen sollten keinerlei Einfluss auf den Heap haben.
An deiner Stelle würde ich einfach ein bisschen experimentieren und nach jeder Änderung den Speicherverbrauch messen. Außerdem solltest du ihn auch an verschiedenen Stellen während der Laufzeit (vor allem während der Initialisierung) messen.

Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

6

28.09.2017, 10:26

Ich habe den Verdacht, dass du da ein wenig over engineering betreibst. Immerhin reden wir hier ja von einem µC (der zudem nicht gerade zu den potentsten gehört). Diese sind zwar prinzipiell vollfertige Computer, aber dennoch limitierte. Ich habe z.B. gelernt, dass dynamische Speicheralloziierung zwar funktioniert, jedoch möglichst vermieden werden sollte. Leider kann ich anhand der Bruchstücke noch nicht wirklich darauf schließen, was genau dein Programm kann bzw können soll, aber ich vermute es ist ein Anwendungsfall von einer FSM, welche sehr häufig auf embedded systems zum Einsatz kommen.
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.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

7

28.09.2017, 15:36

Ich habe mir in C++ ein Eventsystem programmiert. Das kann von meinen Taster, Timer, Encoder und anderen Klassen dazu benutzt werden spezielle Methoden bei Ereignissen aufzurufen. Dazu habe ich mir eine Kapselklasse geschrieben, die Lambdas, Statische Methoden und Membermethoden akzeptiert und diese über eine für alle Arten gleiche Signatur (Der ()-Operator) aufrufbar macht.

[...]

Eine der beiden Methoden wird für jeden Ausgangspin, den es in meinen Programmen gibt einmal aufgerufen. Das sind bei besagtem Programm 50 Stück. Lambdas sind anonyme Klassen, wird dann bei jedem Aufruf eine neue Klassendefinition erstellt oder nur ein neues Objekt erstellt? Also die Klassendefinition wiederverwendet? Sorgt das für den hohen Speicherverbrauch? Gibt es Möglichkeiten dieses System für den RAM zu optimieren ohne mir die Möglichkeit der verschiedenen Delegate-Typen zu verbauen? Flashspeicher habe ich noch genügend!

Zunächst einmal würde ich hinterfragen ob du wirklich so eine Delegate Klasse brauchst, denn deinen Bedarf an dynamischer Speicherallokation zwingst du dir dort effektiv künstlich selbst auf. Wer auch immer einen Delegate erstellt, sollte in der Regel wissen, welche Art von Delegate er genau machen wird. In dem Fall kann dort statt so einem Delegate Objekt auch einfach direkt ein DelegateMember, DelegateStatic oder DelegateLambda gemacht werden und auf einmal brauchen wir kein new mehr... ;)

std::function verwende ich nicht, da ich die Standardbibliothek generell nicht brauche, die frisst mir zu viel Speicher...

Hast du den Speicherverbrauch mal verglichen? Weil effektiv hast du dir da oben einfach nur selbst eine simple Implementierung von std::function gebastelt...


Abgesehen von all dem würde ich von solchen Eventsystemen Abstand nehmen. Ich hatte vor langer, langer Zeit auch mal so eine Phase...nach meiner Erfahrung sind Eventsysteme am Ende des Tages nicht nur furchtbar ineffizient, sondern führen vor allem auch zu völlig undurchschauberem und praktisch unmöglich zu debuggendem Code, wo alles potentiell von überall und gerne auch noch über drei weitere Ecken irgendwie aufgerufen werden kann. Frei nach dem Motto: Kein Kontrollfluss ist uns zu blöd. Als Konsequenz darf man dann überall Checks einbauen um Rekursionen zu detektieren, weil irgendwie, irgendwo, irgendwann bei Vollmond ja was so connected sein könnte, dass ein Event sich indirekt selbst triggered. Und wehe man vergisst mal ein Objekt überall zu deregistrieren bevor es zerstört wird...


PS: Nicht jedes Lambda ist copyable... ;)

Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »dot« (28.09.2017, 16:06)


CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

8

30.09.2017, 20:29

An deiner Stelle würde ich einfach ein bisschen experimentieren und nach jeder Änderung den Speicherverbrauch messen. Außerdem solltest du ihn auch an verschiedenen Stellen während der Laufzeit (vor allem während der Initialisierung) messen.
Jap, das ist eine Idee. Ich werde mal während des Setup etwas detaillierter den Speicherverbrauch messen. Vielleicht finde ich so heraus wo der meiste Speicher angefordert wird.

Ich habe z.B. gelernt, dass dynamische Speicheralloziierung zwar funktioniert, jedoch möglichst vermieden werden sollte.
Der einzige mir bekannte Grund gegen dynamischen Speicher auf deinem µC ist, dass wenn Speicher freigegeben wird mit großer Wahrscheinlichkeit der Speicher nicht wiederverwendet wird. Speicherfragmentierung ist das Resultat. Deshalb habe ich mein Programm so geschrieben, dass wenn dynamischer Speicher angefordert wird, dieser für immer benötigt und verwendet wird. Dynamischer Speicher wird also nicht wieder freigegeben und deshalb dürfte sich mein Heap auch nicht fragmentieren. Sieht für mich auch nicht danach aus, da mein RAM Verbrauch seit 3 Monaten durchgehender Laufzeit immernoch konstant ist. Hast du noch andere Argumente dagegen?

Eine FSM habe ich nicht programmiert. Es ist ein komplexeres Programm was Jalousien, Fenster, Stromversorgungen, Wettermeldungen, Broadcaststeuerung und Licht überwacht und steuert. Die Bibliothek, die ich mir geschrieben habe kommt in insgesamt 10 Geräten zum Einsatz und viele davon haben keine zur Compilezeit festgelegten Strukturen. Die bauen sich anhand einer Konfigdatei von SD-Karte zusammen. Das das trotzdem leicht Overkill für einen µC ist, weiß ich! ;)

Zunächst einmal würde ich hinterfragen ob du wirklich so eine Delegate Klasse brauchst, denn deinen Bedarf an dynamischer Speicherallokation zwingst du dir dort effektiv künstlich selbst auf. Wer auch immer einen Delegate erstellt, sollte in der Regel wissen, welche Art von Delegate er genau machen wird. In dem Fall kann dort statt so einem Delegate Objekt auch einfach direkt ein DelegateMember, DelegateStatic oder DelegateLambda gemacht werden und auf einmal brauchen wir kein new mehr... ;)
Ich könnte auf DelegateMember und DelegateLamda verzichten, aber damit komme ich nur um ein new drumherum. Um Lamdas speichern zu können brauche ich dynamische allokierung. Ich werde das mal ausprobieren.

PS: Nicht jedes Lambda ist copyable... ;)
Meinst du die Kopie im Konstruktor von Delegate? Kann ich das anders lösen?

Abgesehen von all dem würde ich von solchen Eventsystemen Abstand nehmen. Ich hatte vor langer, langer Zeit auch mal so eine Phase...nach meiner Erfahrung sind Eventsysteme am Ende des Tages nicht nur furchtbar ineffizient, sondern führen vor allem auch zu völlig undurchschauberem und praktisch unmöglich zu debuggendem Code, wo alles potentiell von überall und gerne auch noch über drei weitere Ecken irgendwie aufgerufen werden kann. Frei nach dem Motto: Kein Kontrollfluss ist uns zu blöd. Als Konsequenz darf man dann überall Checks einbauen um Rekursionen zu detektieren, weil irgendwie, irgendwo, irgendwann bei Vollmond ja was so connected sein könnte, dass ein Event sich indirekt selbst triggered. Und wehe man vergisst mal ein Objekt überall zu deregistrieren bevor es zerstört wird...
Rekursives triggern ist sehr unwahrscheinlich, weil ich keine langen Eventketten habe. Meistens ruft ein Delegate nur eine Methode auf und die ruft schon gar keine weiteren mehr auf. Maximale Tiefe ist 2 und das kann man das noch leicht überwachen. Wenn das größer wird stimme ich dir aber bei diesem Risiko zu.

Hast du den Speicherverbrauch mal verglichen? Weil effektiv hast du dir da oben einfach nur selbst eine simple Implementierung von std::function gebastelt...
std::function kann doch aber noch viel mehr oder? Zumindest was den Speicherverbrauch angeht... den habe ich am Anfang mal verglichen.
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »CeDoMain« (30.09.2017, 23:39)


Nox

Supermoderator

Beiträge: 5 272

Beruf: Student

  • Private Nachricht senden

9

30.09.2017, 21:22

Pardon, habe mich vermutlich etwas ungeschickt ausgedrückt. Ich meinte nicht, dass du eine FSM geschrieben hast sondern dass deine Anwendung vmtl auch durch eine FSM umgesetzt werden kann. Was gegen dynamische Allokierung halt spricht ist, dass sie "nach außen" deutlich weniger deterministisches Verhalten hervorrufen kann. Je nach Programm wäre es z.B. denkbar, dass 99 Programme so funktionieren wie erwartet und beim 100. fordert man gerade ein wenig mehr Speicher an als verfügbar ist und schon weiß man nicht warum es nicht funktioniert.
Ich arbeite beruflich viel mit µCs und FPGAs. Für eine "Plattformlösung" (sprich möglichst flexbiles Controlling ohne viel Adaptionaufwand für unterschiedliche Peripherie) bin ich nach einigen Experimenten am Ende bei FSM mit OP codes angelangt um einen möglichst flexiblen Programmfluss zu erlauben, ohne dabei die Kernprogrammung zu komplex werden zu lassen. Effektiv handelt es sich um einen einfachen Interpreter. Natürlich verliert man hierdurch an Latenz, aber solange der Interpreter deutlich schneller ausgeführt wird als die Peripherie Daten braucht bzw liefert, ist das unkritisch.
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.

Werbeanzeige