2013-06-12 23 views
7

Czy rekurencyjne funkcje lambda indukują jakiekolwiek obciążenie w porównaniu do zwykłych funkcji rekursywnych (skoro musimy je przechwycić w funkcję std ::)?
Jaka jest różnica między tą funkcją a podobną przy użyciu tylko zwykłych funkcji?Obciążenie rekurencyjnych lambdas

int main(int argc, const char *argv[]) 
{ 
    std::function<void (int)> helloworld = [&helloworld](int count) { 
     std::cout << "Hello world" << std::endl; 
     if (count > 1) helloworld(--count); 
    }; 
    helloworld(2); 
    return 0; 
} 
+2

Twoim głównym problemem będzie mniejsza optymalizacja kompilatora http://ideone.com/QsZVfH – stefan

+0

Nice! Czy masz pomysł, dlaczego? Kompilator nie mógł po prostu odgadnąć, że ten kod jest podobny do zwykłego wywołania funkcji (no-capture ale samego siebie)? – 3XX0

+0

@fscan Muszę, ponieważ jest to funkcja rekurencyjna lambda. Wnioskowanie o typ nie działa tutaj – 3XX0

Odpowiedz

4

Istnieje narzut za pomocą lambdy rekurencyjnie poprzez przechowywanie go jako std::function, chociaż są one w zasadzie się funktory. Wygląda na to, że gcc nie jest w stanie zoptymalizować dobrze, co można zaobserwować w bezpośrednim comparison.

Wdrażanie zachowania lambda, czyli tworzenie funktora, umożliwia ponowną optymalizację gcc. Twój Konkretnym przykładem lambda mogą być realizowane jako

struct HelloWorldFunctor 
{ 
    void operator()(int count) const 
    { 
     std::cout << "Hello world" << std::endl; 
     if (count > 1) 
     { 
     this->operator()(count - 1); 
     } 
    } 
}; 
int main() 
{ 
    HelloWorldFunctor functor; 
    functor(2); 
} 

Dla przykładu stworzyłem funktor będzie wyglądać w tym second demo.

Nawet jeśli ktoś wprowadza połączenia do nieczystych funkcji, takich jak std::rand, wydajność bez rekurencyjne lambda lub z niestandardowym funktora jest jeszcze lepiej. Oto third demo.

Wniosek: przy użyciu numeru std::function jest on narzutowy, choć może być pomijalny w zależności od przypadku użycia. Ponieważ to użycie zapobiega optymalizacji niektórych kompilatorów, nie należy tego używać w znacznym stopniu.

+0

jest prawdą, dziękuję za kod, chociaż podejrzewam, że jest on bardzo specyficzny dla kompilatora.Czy istnieje sposób przekazywania flag kompilatora na ideale? – Pedrom

+0

@Pedrom Nie sądzę, może. ter? Zastrzeżenie: Jeszcze nie intensywnie używałem lambdas, ponieważ projekty, nad którymi pracuję, wymagają kompatybilności wstecznej z kompilatorami, które nie obsługują lambd. – stefan

+0

Próbuję sprawdzić to na mojej maszynie, ale z jakiegoś powodu nie pokazuje mi żadnego wyjścia za pomocą cygwin:/ – Pedrom

2

lambda w C++ są równoważne funktorów więc byłoby tym samym, że wywołanie operatora() pewnej klasie utworzonego automatycznie przez kompilator. Podczas przechwytywania środowiska, co dzieje się za kulisami, przechwycone zmienne są przekazywane do konstruktora klasy i przechowywane jako zmienne składowe.

Krótko mówiąc, różnica w wydajności powinna być bardzo bliska zeru.

Tu jest jakaś dalsze wyjaśnienie: „Jak lambda Zamknięcia Wdrożone”

Skocz do sekcji http://www.cprogramming.com/c++11/c++11-lambda-closures.html

EDIT:

Po kilku badań i dzięki Stefan odpowiedź i kod, okazało się, że nie ma nad głową na rekurencyjnych lambdas powodu idiomu std :: funkcyjnego. Ponieważ lambda musi być zawijana na funkcji std :: w celu wywoływania samej siebie, wymaga ona wywołania funkcji wirtualnej, która powoduje, żedodaje obciążenie.

Przedmiotem traktuje się na komentarzach tej odpowiedzi: https://stackoverflow.com/a/14591727/1112142

+0

Stephan Lavavej przedstawia podobny pomysł [tutaj] (http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/Stephan-T-Lavavej-Core-C-9- z-n) (o 0:38:20). Wydaje się więc, że wywołanie zwykłej funkcji spowodowałoby mniejszy narzut. Pamiętam jednak, że przeczytałem gdzieś, że w niektórych przypadkach to podejście do funktora można zoptymalizować za pomocą wskaźników funkcyjnych. Czy to prawda ? a co z przypadkiem, który podałem? – 3XX0

+0

@ 3XX0 To fajne wideo, dziękuję za link :) Powiedział jednak, że jeśli planujesz używać go w kółko, byłoby sensownie napisać zwykłą funkcję, ale to dlatego, że łatwiej jest wywoływać różne części kod, on nie mówi o wydajności. Jeśli chodzi o funkcję wskaźników funkcyjnych, możesz użyć ich zamiast * lambdas, ale nie sądzę, że będą one tak wygodne, a w praktyce nie zauważyłem żadnej różnicy w wydajności. – Pedrom

+0

@ 3XX0 Jeśli chodzi o twój przykład, to jest dokładnie tak, jak wywołanie funkcji void hello_world (myfunc foo, int count), jeśli jest * jakikolwiek * narzut, który byłby przy pierwszym wywołaniu (ponieważ musisz utworzyć instancję obiektu foo, aby przekazać ją do metody, ale potem wszystkie poniższe wywołania są dokładnie takie same pod względem wydajności, a ponadto, jeśli twoja funkcja rekurencyjna jest metodą, to nie ma żadnej różnicy: – Pedrom

3

Tak więc std::function jest realizowany polimorficznie. Znaczenie kodu jest mniej więcej odpowiednikiem:

struct B 
{ 
    virtual void do(int count) = 0; 
}; 

struct D 
{ 
    B* b; 
    virtual void do(int count) 
    { 
     if (count > 1) 
      b->do(count--); 
    } 
}; 

int main() 
{ 
    D d; 
    d.b = &d; 
    d.do(10); 
} 

Rzadko zdarza się mieć wystarczająco szczelne rekursji tak, że wirtualna metoda wyszukiwania jest znaczna nad głową, ale w zależności od obszaru zastosowania na pewno jest to możliwe.