Ich hätte gerne Informationen darüber, wie man C++11 closures und std::function
in Bezug auf ihre Implementierung und Speicherverwaltung korrekt betrachtet.
Auch wenn ich nicht an voreilige Optimierungen glaube, pflege ich doch den Performance-Einfluss meiner Entscheidungen beim Schreiben neuer Codes sorgfältig zu überdenken. Ich betreibe außerdem eine beträchtliche Menge an echtzeitfähiger Programmierung, z.B. auf Mikrocontrollern und für Audiosysteme, bei denen nicht-deterministische Speicherzuweisungs-Pausen vermieden werden sollten.
Daher möchte ich ein besseres Verständnis dafür entwickeln, wann man C++ Lambdas verwenden sollte oder nicht.
Mein derzeitiges Verständnis ist, dass eine Lambda-Funktion ohne gefangenen Closure genau wie ein C-Callback ist. Wenn jedoch die Umgebung entweder per Wert oder per Referenz eingefangen wird, wird ein anonymer Objekt auf dem Stack erstellt. Wenn ein Value-Closure von einer Funktion zurückgegeben werden muss, wird es in std::function
verpackt. Was passiert in diesem Fall mit dem Closure-Speicher? Wird er vom Stack auf den Heap kopiert? Wird er freigegeben, sobald die std::function
freigegeben wird, d.h. wird er referenziert wie ein std::shared_ptr
?
Ich stelle mir vor, dass ich in einem Echtzeitsystem eine Kette von Lambda-Funktionen einrichten könnte, indem ich B als Fortsetzungsargument an A übergebe, sodass eine Verarbeitungspipeline A->B
erstellt wird. In diesem Fall würden die A- und B-Closures einmal zugeordnet. Ob diese auf dem Stack oder dem Heap zugeordnet werden, bin ich mir jedoch nicht sicher. Doch im Allgemeinen scheint dies in einem Echtzeitsystem sicher zu sein. Andererseits, wenn B eine Lambda-Funktion C erstellt, die es zurückgibt, würde der Speicher für C wiederholt zugeordnet und freigegeben werden, was für eine Echtzeitverwendung nicht akzeptabel wäre.
In Pseudocode eine DSP-Schleife, die ich für echtzeitsicher halte. Ich möchte die Verarbeitungsblöcke A und dann B durchführen, wobei A das Argument aufruft. Beide Funktionen geben std::function
-Objekte zurück, sodass f
ein std::function
-Objekt sein wird, dessen Umgebung im Heap gespeichert ist:
auto f = A(B); // A gibt eine Funktion zurück, die B aufruft
// Memory für die von A zurückgegebene Funktion liegt auf dem Heap?
// Beachten Sie, dass A und B möglicherweise einen Zustand beibehalten
// über mutable Value-Closure!
for (t=0; t<1000; t++) {
y = f(t)
}
Und einer, den ich für ungeeignet in Echtzeitcode halte:
for (t=0; t<1000; t++) {
y = A(B)(t);
}
Und einer, bei dem ich davon ausgehe, dass wahrscheinlich der Stackspeicher für das Closure verwendet wird:
freq = 220;
A = 2;
for (t=0; t<1000; t++) {
y = [=](int t){ return sin(t*freq)*A; }
}
In diesem Fall wird das Closure bei jeder Iteration der Schleife konstruiert, aber anders als im vorherigen Beispiel ist es günstig, weil es genau wie ein Funktionsaufruf ist, es werden keine Heap-Zuweisungen gemacht. Darüber hinaus frage ich mich, ob ein Compiler das Closure "anheben" und Inline-Optimierungen durchführen könnte.
Ist das korrekt? Vielen Dank.