2015-03-05 17 views
5

Mój problem jest dość prosty, chcę użyć lambda w taki sam sposób, w jaki mogę użyć funktora jako "komparatora", pozwól mi wyjaśnić trochę lepiej. Mam dwie duże struktury, obie mają własną implementację operator< i mam również klasę useless (jest to po prostu nazwa klasy w kontekście tego pytania), które używają dwóch struktur, wszystko wygląda tak:Używanie lambda zamiast obiektu funkcji, zła wydajność

struct be_less 
{ 
    //A lot of stuff 
    int val; 
    be_less(int p_v):val(p_v){} 
    bool operator<(const be_less& p_other) const 
    { 
     return val < p_other.val; 
    } 
}; 

struct be_more 
{ 
    //A lot of stuff 
    int val; 
    be_more(int p_v):val(p_v){} 
    bool operator<(const be_more& p_other) const 
    { 
     return val > p_other.val; 
    } 
}; 

class useless 
{ 
    priority_queue<be_less> less_q; 
    priority_queue<be_more> more_q; 
public: 
    useless(const vector<int>& p_data) 
    { 
     for(auto elem:p_data) 
     { 
      less_q.emplace(elem); 
      more_q.emplace(elem); 
     } 
    } 
}; 

whould chciałbym usunąć powielania w dwóch struct tych, najprostszy pomysł jest, aby struct szablonu i dostarczyć dwa funktor, aby wykonać zadanie porównania:

template<typename Comp> 
struct be_all 
{ 
    //Lot of stuff, better do not duplicate 
    int val; 
    be_all(int p_v):val{p_v}{} 
    bool operator<(const be_all<Comp>& p_other) const 
    { 
     return Comp()(val,p_other.val); 
    } 
}; 

class comp_less 
{ 
public: 
    bool operator()(int p_first, 
        int p_second) 
    { 
     return p_first < p_second; 
    } 
}; 

class comp_more 
{ 
public: 
    bool operator()(int p_first, 
        int p_second) 
    { 
     return p_first > p_second; 
    } 
}; 

typedef be_all<comp_less> all_less; 
typedef be_all<comp_more> all_more; 

class useless 
{ 
    priority_queue<all_less> less_q; 
    priority_queue<all_more> more_q; 
public: 
    useless(const vector<int>& p_data) 
    { 
     for(auto elem:p_data) 
     { 
      less_q.emplace(elem); 
      more_q.emplace(elem); 
     } 
    } 
}; 

tę pracę bardzo dobrze , teraz na pewno nie mam żadnych duplikatów w kodzie struct w cenie dwóch dodatkowych obiektów funkcji. Zauważ, że bardzo upraszczam implementację operator<, hipotetyczny rzeczywisty kod robi o wiele więcej niż tylko porównywanie dwóch int.

Potem myślał o tym, jak zrobić to samo przy użyciu lambda (podobnie jak w eksperymencie) tylko .Powierzchnia roztwór roboczy byłem w stanie realizować to:

template<typename Comp> 
struct be_all 
{ 
    int val; 
    function<bool(int,int)> Comparator; 
    be_all(Comp p_comp,int p_v): 
     Comparator(move(p_comp)), 
     val{p_v} 
    {} 
    bool operator<(const be_all& p_other) const 
    { 
     return Comparator(val, p_other.val); 
    } 
}; 

auto be_less = [](int p_first, 
      int p_second) 
{ 
    return p_first < p_second; 
}; 

auto be_more = [](int p_first, 
      int p_second) 
{ 
    return p_first > p_second; 
}; 

typedef be_all<decltype(be_less)> all_less; 
typedef be_all<decltype(be_more)> all_more; 

class useless 
{ 
    priority_queue<all_less> less_q; 
    priority_queue<all_more> more_q; 
public: 
    useless(const vector<int>& p_data) 
    { 
     for(auto elem:p_data) 
     { 
      less_q.emplace(be_less,elem); 
      more_q.emplace(be_more,elem); 
     } 
    } 
}; 

Ta implementacja nie tylko dodać nowego członka do danych zawierających strukturę, ale również bardzo słabej wydajności, przygotowałem mały test, w którym tworzę jedną instancję dla wszystkich nieprzydatnych klas, które tu pokazałem, za każdym razem, gdy karmię konstruktora wektorem pełnym 2 milionów liczby całkowite, wyniki są następujące:

  1. Trwa 48 ms xecute konstruktora pierwszego bezużyteczne klasy
  2. trwa 228ms aby utworzyć drugą bezużyteczną klasę (funktora)
  3. Staje 557ms, aby utworzyć trzecią bezużyteczną klasę (lambda)

wyraźnie cenę płaci się za usunięte Powielanie jest bardzo wysokie, a w oryginalnym kodzie powielanie wciąż istnieje. Proszę zwrócić uwagę, jak kiepska jest wydajność trzeciej implementacji, dziesięciokrotnie wolniejsza niż oryginalna, uważam, że trzecia implementacja jest wolniejsza niż druga, z powodu dodatkowego parametru w konstruktorze be_all ... ale:

Właściwie istnieje również czwarty przypadek, gdzie nadal stosowany lambda ale pozbyć się członka Comparator i dodatkowego parametru w be_all kod jest następujący:

template<typename Comp> 
struct be_all 
{ 
    int val; 
    be_all(int p_v):val{p_v} 
    {} 
    bool operator<(const be_all& p_other) const 
    { 
     return Comp(val, p_other.val); 
    } 
}; 

bool be_less = [](int p_first, 
      int p_second) 
{ 
    return p_first < p_second; 
}; 

bool be_more = [](int p_first, 
      int p_second) 
{ 
    return p_first > p_second; 
}; 

typedef be_all<decltype(be_less)> all_less; 
typedef be_all<decltype(be_more)> all_more; 

class useless 
{ 
    priority_queue<all_less> less_q; 
    priority_queue<all_more> more_q; 
public: 
    useless(const vector<int>& p_data) 
    { 
     for(auto elem:p_data) 
     { 
      less_q.emplace(elem); 
      more_q.emplace(elem); 
     } 
    } 
}; 

Jeśli usunąć auto od lambda i użyj bool zamiast kodu zbuduj, nawet jeśli użyję Comp(val, p_other.val) w operator<.

Co jest bardzo dziwne dla mnie jest to, że ta czwarta realizacja (lambda bez członu Comparator) jest jeszcze wolniej niż inne, na końcu średnia wydajność udało mi się zarejestrować, są następujące:

  1. 48MS
  2. 228ms
  3. 557ms
  4. 698ms

Dlaczego funktora w tym scenariuszu są znacznie szybsze niż lambda? Spodziewałem się, że lambda będzie przynajmniej dobrze grała jako zwykły funktor, czy ktoś z was może skomentować? Czy istnieje jakiś techniczny powód, dla którego czwarta implementacja jest wolniejsza od trzeciej?

PS:

kompilator używam jest g ++ 4.8.2 z -O3. W moim teście i utworzyć dla każdej klasy useless instancji i za pomocą stopera i uwzględnienia Wymagany czas:

namespace benchmark 
{ 
    template<typename T> 
    long run() 
    { 
     auto start=chrono::high_resolution_clock::now(); 
     T t(data::plenty_of_data); 
     auto stop=chrono::high_resolution_clock::now(); 
     return chrono::duration_cast<chrono::milliseconds>(stop-start).count(); 
    } 
} 

oraz:

cout<<"Bad code: "<<benchmark::run<bad_code::useless>()<<"ms\n"; 
cout<<"Bad code2: "<<benchmark::run<bad_code2::useless>()<<"ms\n"; 
cout<<"Bad code3: "<<benchmark::run<bad_code3::useless>()<<"ms\n"; 
cout<<"Bad code4: "<<benchmark::run<bad_code4::useless>()<<"ms\n"; 

Zbiór liczb wejściowych jest taka sama dla wszystkich, plenty_of_data jest wektorem pełnym 2 milionów intergerów.

Dzięki za poświęcony czas

+2

TL; DR: Porównywalny funktor ma zwykle co najmniej jednego "operator boola" (T, U) "oceniający" na mniej niż " –

+2

Jestem leniwy. Czy możesz opublikować jeden blok kodu, który zawiera 4 zmierzone przypadki, wyraźnie zaznaczone, że mogę wykonać w moim własnym kompilatorze w jednej kopii i wkleić? –

Odpowiedz

4

Nie jesteś porównując czas pracy lambda i funktora. Zamiast tego liczby wskazują na różnicę w używaniu funktora i std::function. Na przykład std::function<R(Args...)> może przechowywać dowolne Callable spełniające sygnaturę R(Args...). Czyni to poprzez usuwanie typu. Tak więc, różnica, którą widzisz, pochodzi od kosztów połączenia wirtualnego w std::function::operator(). Na przykład implementacja libc++ (3.5) ma klasę podstawową template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes> __base z virtual operator(). std::function przechowuje __base<...>*. Po utworzeniu std::function z opcją wywołania F tworzony jest obiekt typu template<class F, class _Alloc, class R, class ...Args> class __func, który dziedziczy po __base<...> i zastępuje wirtualny operator().

+0

Czy sugerujesz, że czwarta implementacja korzysta z funkcji std :: w jakiś sposób niejawnie? 'Comp' jest rozwiązany std :: function? Jak widać 'be_all' nie deklaruje żadnej funkcji std ::. – fjanisze

+2

Mam na myśli implementację: 'szablon struct be_all { int val; funkcja Komparator; 'w nim. Jeśli jest więcej części na pytanie, przyznam, że mogłem go pominąć, biorąc pod uwagę długość pytania. – Pradhan

Powiązane problemy