web-dev-qa-db-fra.com

Pourquoi les lambdas peuvent-ils être mieux optimisés par le compilateur que les fonctions simples?

Dans son livre The C++ Standard Library (Second Edition) Nicolai Josuttis déclare que les lambdas peuvent être mieux optimisés par le compilateur que les fonctions simples.

De plus, les compilateurs C++ optimisent mieux les lambdas que les fonctions ordinaires. (Page 213)

Pourquoi donc?

Je pensais qu'en matière d'inline, il ne devrait plus y avoir de différence. La seule raison pour laquelle je pourrais penser est que les compilateurs pourraient avoir un meilleur contexte local avec les lambdas et que cela pourrait faire plus d'hypothèses et effectuer plus d'optimisations.

165
inf

La raison en est que les lambdas sont objets de fonction donc les passer à un modèle de fonction instanciera une nouvelle fonction spécifiquement pour cet objet. Le compilateur peut donc insérer trivialement l'appel lambda.

Pour les fonctions, en revanche, l'ancienne mise en garde s'applique: une fonction pointeur est transmise au modèle de fonction, et les compilateurs ont traditionnellement beaucoup de problèmes pour aligner les appels via des pointeurs de fonction. Ils peuvent théoriquement être alignés, mais seulement si la fonction environnante est également alignée.

À titre d'exemple, considérons le modèle de fonction suivant:

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

L'appeler avec un lambda comme celui-ci:

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

Résultats dans cette instanciation (créée par le compilateur):

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

… Le compilateur connaît _some_lambda_type::operator () et peut incorporer les appels de manière triviale. (Et invoquer la fonction map avec any autre lambda créerait une nouvelle instanciation de map puisque chaque lambda a un type distinct.)

Mais lorsqu'elle est appelée avec un pointeur de fonction, l'instanciation se présente comme suit:

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

… Et ici f pointe vers une adresse différente pour chaque appel à map et donc le compilateur ne peut pas aligner les appels à f sauf si l'appel environnant à map a a également été intégré afin que le compilateur puisse résoudre f en une fonction spécifique.

168
Konrad Rudolph

Parce que lorsque vous passez une "fonction" à un algorithme, vous passez en fait un pointeur à la fonction, il doit donc effectuer un appel indirect via le pointeur vers la fonction. Lorsque vous utilisez un lambda, vous passez un objet à une instance de modèle spécialement instanciée pour ce type et l'appel à la fonction lambda est un appel direct, pas un appel via un pointeur de fonction, il est donc plus probable qu'il soit intégré.

25
jcoder