Chciałbym uzyskać informacje o tym, jak poprawnie myśleć o domknięciach std::function
w C ++ 11 oraz o tym, jak są one implementowane i jak obsługiwana jest pamięć.
Chociaż nie wierzę w przedwczesną optymalizację, mam zwyczaj uważnego rozważania wpływu moich wyborów na wydajność podczas pisania nowego kodu. Zajmuję się również sporą ilością programowania w czasie rzeczywistym, np. Na mikrokontrolerach i systemach audio, gdzie należy unikać niedeterministycznych przerw w przydzielaniu / zwalnianiu pamięci.
Dlatego chciałbym lepiej zrozumieć, kiedy używać lambd języka C ++, a kiedy nie.
Obecnie rozumiem, że lambda bez przechwyconego zamknięcia jest dokładnie taka sama, jak wywołanie zwrotne w C. Jednak gdy środowisko jest przechwytywane przez wartość lub przez odniesienie, na stosie jest tworzony anonimowy obiekt. Kiedy zamknięcie wartości musi zostać zwrócone z funkcji, należy je opakować std::function
. Co dzieje się z pamięcią zamknięcia w tym przypadku? Czy jest kopiowany ze stosu na stos? Czy jest uwalniany za każdym razem, gdy std::function
zostanie uwolniony, tj. Czy jest liczony jako odniesienie std::shared_ptr
?
Wyobrażam sobie, że w systemie czasu rzeczywistego mógłbym ustawić łańcuch funkcji lambda, przekazując B jako argument kontynuacji do A, aby utworzyć potok przetwarzania A->B
. W takim przypadku zamknięcia A i B zostaną przydzielone raz. Chociaż nie jestem pewien, czy zostaną one przydzielone na stosie, czy na stercie. Jednak ogólnie wydaje się to bezpieczne w użyciu w systemie czasu rzeczywistego. Z drugiej strony, jeśli B konstruuje jakąś funkcję lambda C, którą zwraca, wówczas pamięć dla C byłaby wielokrotnie przydzielana i zwalniana, co byłoby nie do przyjęcia w przypadku użycia w czasie rzeczywistym.
W pseudokodzie, pętla DSP, która moim zdaniem będzie bezpieczna w czasie rzeczywistym. Chcę wykonać blok przetwarzania A, a następnie B, gdzie A wywołuje swój argument. Obie te funkcje zwracają std::function
obiekty, więc f
będzie to std::function
obiekt, którego środowisko jest przechowywane na stercie:
auto f = A(B); // A returns a function which calls B
// Memory for the function returned by A is on the heap?
// Note that A and B may maintain a state
// via mutable value-closure!
for (t=0; t<1000; t++) {
y = f(t)
}
I myślę, że może być zły w kodzie czasu rzeczywistego:
for (t=0; t<1000; t++) {
y = A(B)(t);
}
I taki, w którym myślę, że pamięć stosu jest prawdopodobnie używana do zamknięcia:
freq = 220;
A = 2;
for (t=0; t<1000; t++) {
y = [=](int t){ return sin(t*freq)*A; }
}
W tym drugim przypadku zamknięcie jest konstruowane przy każdej iteracji pętli, ale w przeciwieństwie do poprzedniego przykładu jest tanie, ponieważ jest podobne do wywołania funkcji, nie są dokonywane alokacje sterty. Co więcej, zastanawiam się, czy kompilator mógłby „podnieść” zamknięcie i wprowadzić optymalizacje wbudowane.
Czy to jest poprawne? Dziękuję Ci.
operator()
. Nie ma żadnego „podnoszenia” do wykonania, lambdy nie są niczym specjalnym. Są tylko krótką ręką dla obiektu funkcji lokalnej.