2013-09-04 23 views
20

Questo è stato già toccato in Why C++ lambda is slower than ordinary function when called multiple times? e C++0x Lambda overhead Ma penso che il mio esempio sia un po 'diverso dalla discussione nel primo e contraddice il risultato nel secondo.Comprendere il sovraccarico delle funzioni lambda in C++ 11

Nella ricerca di un collo di bottiglia nel mio codice, ho trovato una funzione modello occulto che elabora una lista di argomenti variadici con una determinata funzione del processore, come la copia del valore in un buffer.

template <typename T> 
void ProcessArguments(std::function<void(const T &)> process) 
{} 

template <typename T, typename HEAD, typename ... TAIL> 
void ProcessArguments(std::function<void(const T &)> process, const HEAD &head, const TAIL &... tail) 
{ 
    process(head); 
    ProcessArguments(process, tail...); 
} 

ho confrontato il tempo di esecuzione di un programma che utilizza questo codice insieme con una funzione lambda così come una funzione globale che copia gli argomenti in un buffer globale utilizzando un puntatore in movimento:

int buffer[10]; 
int main(int argc, char **argv) 
{ 
    int *p = buffer; 

    for (unsigned long int i = 0; i < 10E6; ++i) 
    { 
    p = buffer; 
    ProcessArguments<int>([&p](const int &v) { *p++ = v; }, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
    } 
} 

compilato con g ++ 4.6 e -O3 misurando con il tempo utensile impiega più di 6 secondi sulla mia macchina mentre

int buffer[10]; 
int *p = buffer; 
void CopyIntoBuffer(const int &value) 
{ 
    *p++ = value; 
} 

int main(int argc, char **argv) 
{ 
    int *p = buffer; 

    for (unsigned long int i = 0; i < 10E6; ++i) 
    { 
    p = buffer; 
    ProcessArguments<int>(CopyIntoBuffer, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
    } 

    return 0; 
} 

richiede circa 1,4 secondi.

Non capisco cosa succede dietro le quinte che spiega il tempo trascorso e mi chiedo se posso cambiare qualcosa per utilizzare le funzioni lambda senza pagare con il runtime.

+0

Così è quello globale davvero più lento? Dici che è 6s contro 1,4 per quello basato su lambda, ma poi l'ultima frase non ha senso. – dasblinkenlight

+0

Durante l'analisi, hai sbirciato in un elenco di assiemi generato? – WhozCraig

+0

Passare 'process' da' const' riferimento, come questo, 'void ProcessArguments (const std :: function & process)', fare qualche differenza? – dasblinkenlight

risposta

33

Il problema qui è l'utilizzo di std :: function. Lo si invia per copia e quindi ne copia il contenuto (e lo fa in modo ricorsivo quando si rilasciano i parametri).

Ora, per il puntatore alla funzione, il contenuto è, beh, solo il puntatore alla funzione. Per lambda, il contenuto è almeno un puntatore alla funzione + riferimento che è stato catturato. Questo è il doppio da copiare. Inoltre, a causa della cancellazione di tipo di std :: function, la copia di qualsiasi dato sarà molto più lenta (non inline).

Ci sono diverse opzioni qui, e il migliore probabilmente passerebbe non std :: function, ma template. I vantaggi sono che la chiamata al metodo è più probabile che sia in linea, nessun tipo di cancellazione avviene con std :: function, nessuna copia avviene, tutto è molto buono. Come quella:

template <typename TFunc> 
void ProcessArguments(const TFunc& process) 
{} 

template <typename TFunc, typename HEAD, typename ... TAIL> 
void ProcessArguments(const TFunc& process, const HEAD &head, const TAIL &... tail) 
{ 
    process(head); 
    ProcessArguments(process, tail...); 
} 

Seconda opzione sta facendo lo stesso, ma l'invio del process dalla copia. Ora, la copia avviene, ma è ancora ordinatamente in linea.

Ciò che è altrettanto importante è che il corpo process 'può anche essere inarcato, in particolare per lamda. A seconda della complessità della copia dell'oggetto lambda e delle sue dimensioni, il passaggio per copia può essere o meno più veloce del passaggio per riferimento. Potrebbe essere più veloce perché il compilatore potrebbe avere un ragionamento temporale più difficile sul riferimento rispetto alla copia locale.

template <typename TFunc> 
void ProcessArguments(TFunc process) 
{} 

template <typename TFunc, typename HEAD, typename ... TAIL> 
void ProcessArguments(TFunc process, const HEAD &head, const TAIL &... tail) 
{ 
    process(head); 
    ProcessArguments(process, tail...); 
} 

terza opzione è, beh, provate passando std :: funzione <> per riferimento. In questo modo si evita almeno di copiare, ma le chiamate non saranno in linea.

Ecco alcuni risultati perf (utilizzando il compilatore C++ 11 di ideoni). Si noti che, come previsto, inline corpo lambda si sta dando le migliori prestazioni:

Original function: 
0.483035s 

Original lambda: 
1.94531s 


Function via template copy: 
0.094748 

### Lambda via template copy: 
0.0264867s 


Function via template reference: 
0.0892594s 

### Lambda via template reference: 
0.0264201s 


Function via std::function reference: 
0.0891776s 

Lambda via std::function reference: 
0.09s 
+0

Una lettura interessante! Mi chiedo solo, perché suggeriresti lambda tramite copia modello? Voglio dire, cosa c'è di sbagliato con riferimento? ci sono dei vantaggi nell'aggiungere la copia? – guyarad

+0

Non ho suggerito di copiare sul riferimento :) Quello che ho detto è che ci sono tutte quelle opzioni. Forse non era molto chiaro. E non puoi essere sicuro di quale dei due (passando lambda per riferimento o per copia) è meglio. A seconda del contenuto del lambda (la sua dimensione), la sua complessità di copiatura, passando per riferimento può o meno essere più veloce. I compilatori possono avere un ragionamento temporale più difficile sui riferimenti rispetto agli oggetti passati per valore. – biocomp

+0

forse è perché non sono un madrelingua inglese, ma questa frase mi ha confuso: "potresti davvero ** fare lo stesso" (l'enfasi è mia). La parola "in realtà" mi ha portato a pensare che volevi dire che è meglio ... grazie per aver chiarito. – guyarad

Problemi correlati