2015-03-05 11 views
5

Il mio problema è piuttosto semplice, voglio usare lambda nello stesso modo in cui posso usare un funtore come un "comparatore", lasciatemi spiegare un po 'meglio. Ho due grandi strutture, entrambe hanno la loro implementazione di operator< e ho anche una classe useless (questo è solo il nome della classe nel contesto di questa domanda) che usa le due struct, tutto sembra così:Uso di lambda invece di un oggetto funzione, cattiva prestazione

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); 
     } 
    } 
}; 

mi whould piace per rimuovere la duplicazione nei due struct di, l'idea più semplice è quello di rendere la struct un modello e fornire due funtore per fare il lavoro di confronto:

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); 
     } 
    } 
}; 

Questo lavoro abbastanza bene , ora di sicuro non ho alcuna duplicazione nel codice della struttura al prezzo di due oggetti funzione aggiuntivi. Si noti che sto semplificando molto l'implementazione di operator<, il codice reale dell'hipotetic fa molto di più del semplice confronto di due inte.

Poi stavo pensando a come fare la stessa cosa usando lambda (Proprio come un esperimento) .La unica soluzione di lavoro sono stato in grado di realizzare è:

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); 
     } 
    } 
}; 

Questa implementazione non solo aggiungere un nuovo membro ai dati contenenti struct, ma anche una prestazione molto scarsa, ho preparato un piccolo test in cui creo un'istanza per tutta la classe inutile che ti ho mostrato qui, ogni volta che alimento il costruttore con un vettore pieno di 2 milioni interi, i risultati sono i seguenti:

  1. Da 48 ms a e xecute il costruttore della prima classe inutile
  2. prende 228ms per creare la seconda classe inutile (functor)
  3. prende 557ms per creare la terza classe inutile (lambda)

Chiaramente il prezzo che pagare per la rimozione la duplicazione è molto alta e nel codice originale la duplicazione è ancora lì. Si noti quanto le prestazioni della terza implementazione siano scadenti, dieci volte più lenta di quella originale, credevo che il motivo per cui la terza implementazione fosse più lenta del secondo era a causa del parametro aggiuntivo nel costruttore di be_all ... ma:

in realtà c'è anche un quarto caso, dove ho usato ancora il lambda, ma mi libero degli Stati Comparator e del parametro aggiuntivo nel be_all, il codice è il seguente:

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); 
     } 
    } 
}; 

Se rimuovo auto dal lambda e utilizzare bool invece del codice build anche se uso Comp(val, p_other.val) in operator<.

Qual è molto strano per me è che questo quarto implementazione (lambda senza il membro Comparator) è ancora più lento rispetto agli altri, alla fine della performance media sono stato in grado di registrare sono i seguenti:

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

Perché il funtore sono molto più veloci di lambda in questo scenario? Mi aspettavo che i lambda funzionassero almeno come un normale funtore, qualcuno può commentare per favore? E c'è qualche ragione tecnica per cui la quarta implementazione è più lenta della terza?

PS:

Il compilator sto usando è g ++ 4.8.2 con -O3. Nella mia prova ho creato per ogni classe useless un'istanza e utilizzare crono prendo in considerazione il tempo necessario:

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(); 
    } 
} 

e:

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"; 

l'insieme degli interi di ingresso è la stessa per tutti, plenty_of_data è un vettore pieno di 2 milioni di interger.

Grazie per il vostro tempo

+2

TL; DR: un functor di confronto di solito ha uno o più 'bool operator() (T, U)' che sta valutando 'minore di' –

+2

Sono pigro. Puoi pubblicare un blocco di codice che contiene i 4 casi misurati, chiaramente contrassegnati, che posso eseguire nel mio compilatore in una copia e incolla? –

risposta

4

Non stanno confrontando il tempo di esecuzione di un lambda e un funtore. Invece, i numeri indicano la differenza nell'uso di un funtore e di un std::function. E std::function<R(Args...)>, ad esempio, è possibile memorizzare qualsiasi Callable soddisfacendo la firma R(Args...). Lo fa attraverso la cancellazione del tipo. Quindi, la differenza che vedi proviene dal sovraccarico di una chiamata virtuale in std::function::operator().

Ad esempio, l'implementazione libc++ (3.5) ha una classe base template<class _Fp, class _Alloc, class _Rp, class ..._ArgTypes> __base con un virtual operator(). std::function memorizza un __base<...>*. Ogni volta che si crea un con un callable F, viene creato un oggetto di tipo template<class F, class _Alloc, class R, class ...Args> class __func, che eredita da __base<...> e sovrascrive il numero virtuale operator().

+0

Stai suggerendo che la quarta implementazione usi in qualche modo implicitamente la funzione std ::? 'Comp' è risolto std :: function? Come puoi vedere 'be_all' non dichiara alcuna funzione std ::. – fjanisze

+2

Mi riferisco all'implementazione con questo: 'template struct be_all { int val; function Comparatore; 'in esso. Se ci sono più parti della domanda, ammetto che avrei potuto mancare data la lunghezza della domanda. – Pradhan

Problemi correlati