Perché lambdas può essere ottimizzato meglio dal compilatore rispetto alle semplici funzioni?


171

Nel suo libro The C++ Standard Library (Second Edition)Nicolai Josuttis afferma che lambdas può essere ottimizzato meglio dal compilatore rispetto alle semplici funzioni.

Inoltre, i compilatori C ++ ottimizzano le lambda meglio di quanto facciano le normali funzioni. (Pagina 213)

Perché?

Ho pensato che quando si trattava di sottolineare non ci sarebbero più differenze. L'unico motivo che mi viene in mente è che i compilatori potrebbero avere un contesto locale migliore con lambdas e tali possono fare più ipotesi ed eseguire più ottimizzazioni.



Fondamentalmente, l'istruzione si applica a tutti gli oggetti funzione , non solo a lambda.
Newacct,

4
Sarebbe errato perché anche i puntatori a funzione sono oggetti funzione.
Johannes Schaub -

2
@litb: penso di non essere d'accordo con questo. ^ W ^ W ^ W ^ W ^ W ^ W (dopo aver esaminato lo standard) Non ero a conoscenza di quel ismo C ++, anche se penso nel linguaggio comune (e secondo wikipedia), le persone significano istanza di qualche classe richiamabile quando dicono oggetto funzione.
Sebastian Mach,

1
Alcuni compilatori possono ottimizzare al meglio lambda rispetto alle semplici funzioni, ma non tutti :-(
Cody Grey

Risposte:


175

Il motivo è che le lambda sono oggetti funzione, quindi passandoli a un modello di funzione verrà creata un'istanza di una nuova funzione specifica per quell'oggetto. Il compilatore può quindi banalmente incorporare la chiamata lambda.

Per le funzioni, d'altra parte, si applica il vecchio avvertimento: un puntatore a funzione viene passato al modello di funzione, e tradizionalmente i compilatori hanno molti problemi nell'allineare le chiamate tramite puntatori a funzione. In teoria possono essere incorporati, ma solo se anche la funzione circostante è incorporata.

Ad esempio, considerare il seguente modello di funzione:

template <typename Iter, typename F>
void map(Iter begin, Iter end, F f) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

Chiamandolo con un lambda come questo:

int a[] = { 1, 2, 3, 4 };
map(begin(a), end(a), [](int n) { return n * 2; });

Risultati in questa istanza (creato dal compilatore):

template <>
void map<int*, _some_lambda_type>(int* begin, int* end, _some_lambda_type f) {
    for (; begin != end; ++begin)
        *begin = f.operator()(*begin);
}

... il compilatore conosce _some_lambda_type::operator ()e può incorporare le chiamate in modo banale. (E invocare la funzione mapcon qualsiasi altra lambda creerebbe una nuova istanza mappoiché ogni lambda ha un tipo distinto.)

Ma quando viene chiamato con un puntatore a funzione, l'istanza appare come segue:

template <>
void map<int*, int (*)(int)>(int* begin, int* end, int (*f)(int)) {
    for (; begin != end; ++begin)
        *begin = f(*begin);
}

... e qui findica un indirizzo diverso per ogni chiamata a mape quindi il compilatore non può incorporare le chiamate a fmeno che anche la chiamata circostante a mapsia stata incorporata in modo che il compilatore possa risolvere funa specifica funzione.


4
Forse vale la pena ricordare che l'istanza dello stesso modello di funzione con un'espressione lambda diversa creerà una funzione completamente nuova con un tipo unico, che potrebbe essere un inconveniente.
Freddo,

2
@greggo Assolutamente. Il problema è quando si gestiscono funzioni che non possono essere integrate (perché troppo grandi). Qui la chiamata al callback può ancora essere incorporata nel caso di un lambda, ma non nel caso di un puntatore a funzione. std::sortè il classico esempio di questo che usa lambda invece di un puntatore a funzione qui porta a un aumento delle prestazioni di sette volte (probabilmente di più, ma non ho dati su questo!).
Konrad Rudolph

1
@greggo Ti stai confondendo due funzioni qui: quello che stiamo passando il lambda a (ad esempio std::sort, o mapnel mio esempio) e lambda stessa. La lambda è di solito piccola. L'altra funzione - non necessariamente. Ci occupiamo di allineare le chiamate alla lambda all'interno dell'altra funzione.
Konrad Rudolph,

2
@greggo lo so. Questo è letteralmente ciò che dice l'ultima frase della mia risposta.
Konrad Rudolph

1
Quello che trovo curioso (essendoci appena imbattuto su di esso) è che data una semplice funzione booleana la predcui definizione è visibile, e usando gcc v5.3, std::find_if(b, e, pred)non è in linea pred, ma lo std::find_if(b, e, [](int x){return pred(x);})fa. Clang riesce a incorporare entrambi, ma non produce codice veloce come g ++ con lambda.
rici,

26

Perché quando si passa una "funzione" a un algoritmo si passa effettivamente a un puntatore per funzionare, quindi deve fare una chiamata indiretta tramite il puntatore alla funzione. Quando si utilizza un lambda, si passa un oggetto a un'istanza del modello appositamente istanziata per quel tipo e la chiamata alla funzione lambda è una chiamata diretta, non una chiamata tramite un puntatore a funzione, quindi è molto probabile che sia incorporata.


5
"la chiamata alla funzione lambda è una chiamata diretta" - anzi. E la stessa cosa vale per tutti gli oggetti funzione, non solo per i lambda. Si tratta solo di puntatori di funzioni che non possono essere incorporati con la stessa facilità, se non del tutto.
Pete Becker,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.