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
Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.
... und diese Signatur kürzer!
- übersichtlicher
- logischer
- verständlicher
Administrator
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.Womit misst du denn den Speicherverbrauch?
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...Forderst du dynamisch Speicher an?
Meinte nicht, dass der Code als String gespeichert wird. 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.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?
Der Speicher wird wie in diesem Modell beschrieben verwaltet. Würde deine Frage also mit Ja beantworten.Wird von der Speicherverwaltung vielleicht der gesamte RAM als "Pool" für Allokierungen genutzt?
Danke, das bringt eine Menge Klarheit!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...
Vorneweg: Falls das ein zu großes neues Fass auf macht, erstelle ich gerne einen neuen Thread dafür!
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. Der Programmcode der Delegate-Klassen sieht so aus:
C-/C++-Quelltext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 template<typename R = void, typename ... Ps> class DelegateBase { // Operatoren public: virtual R operator()(Ps ...) = 0; }; template<typename R = void, typename ... Ps> class Delegate { // Felder private: DelegateBase<R, Ps ...>* internal; // Konstruktor public: Delegate() : internal(0) { } template<typename T> Delegate(T* obj, R (T::*fp)(Ps ...)) : internal(new DelegateMember<T, R, Ps ...>(obj, fp)) { } Delegate(R (*fp)(Ps ...)) : internal(new DelegateStatic<R, Ps ...>(fp)) { } template<typename T> Delegate(T expr) : internal(new DelegateLambda<T, R, Ps ...>(expr)) { } // Operatoren public: R operator()(Ps ... params) { return (*internal)(params ...); } };
C-/C++-Quelltext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 template<typename T, typename R = void, typename ... Ps> class DelegateMember : public DelegateBase<R, Ps ...> { // Felder private: T* Obj; R (T::*Fp)(Ps ...); // Konstruktor public: DelegateMember(T* obj, R (T::*fp)(Ps ...)) : Obj(obj), Fp(fp) { } // Methoden public: virtual R operator()(Ps ... params) { return (Obj->*Fp)(params ...); } }; template<typename T, typename R = void, typename ... Ps> class DelegateLambda : public DelegateBase<R, Ps ...> { // Felder private: T Expr; // Konstruktor public: DelegateLambda(T expr) : Expr(expr) { } // Methoden public: virtual R operator()(Ps ... params) { return Expr(params ...); } }; template<typename R = void, typename ... Ps> class DelegateStatic : public DelegateBase<R, Ps ...> { // Felder private: R (*Fp)(Ps ...); // Konstruktor public: DelegateStatic(R (*fp)(Ps ...)) : Fp(fp) { } // Methoden public: virtual R operator()(Ps ... params) { return Fp(params ...); } };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!
C-/C++-Quelltext
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using DigitalOut = Delegate<void, bool>; DigitalOut* ArduinoMega::GetDigitalOut(const DigitalPin pin) { pinMode(pin, OUTPUT); return new DigitalOut([pin](const bool value) { digitalWrite(pin, value ? HIGH : LOW); }); } DigitalOut* MCP23017::GetDigitalOut(const IOPort pin) { PinMode(pin, OUTPUT); return new DigitalOut([this, pin](const bool value) { if(value) SET(Output, pin); else UNSET(Output, pin); WriteWordRegister(REG_OUTPUT, Output); }); }
Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.
... und diese Signatur kürzer!
- übersichtlicher
- logischer
- verständlicher
Administrator
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?
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!
std::function verwende ich nicht, da ich die Standardbibliothek generell nicht brauche, die frisst mir zu viel Speicher...
Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »dot« (28.09.2017, 16:06)
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.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.
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?Ich habe z.B. gelernt, dass dynamische Speicheralloziierung zwar funktioniert, jedoch möglichst vermieden werden sollte.
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.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...
Meinst du die Kopie im Konstruktor von Delegate? Kann ich das anders lösen?PS: Nicht jedes Lambda ist copyable...
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.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...
std::function kann doch aber noch viel mehr oder? Zumindest was den Speicherverbrauch angeht... den habe ich am Anfang mal verglichen.Hast du den Speicherverbrauch mal verglichen? Weil effektiv hast du dir da oben einfach nur selbst eine simple Implementierung von std::function gebastelt...
Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.
... und diese Signatur kürzer!
- übersichtlicher
- logischer
- verständlicher
Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »CeDoMain« (30.09.2017, 23:39)
Werbeanzeige