J'ai entendu sur un forum utilisant std::function<>
entraîne une baisse des performances. Est-ce vrai ? Si c'est le cas, la baisse des performances est-elle importante ?
Réponses
Trop de publicités?Il y a, en effet, des problèmes de performance avec std:function
dont il faut tenir compte lors de son utilisation. Le principal atout de std::function
Le mécanisme d'effacement des types n'est pas gratuit, et nous pouvons (mais ne devons pas nécessairement) en payer le prix.
std::function
est une classe modèle qui enveloppe les types appelables. Cependant, elle n'est pas paramétrée sur le type appelable lui-même mais seulement sur ses types de retour et d'argument. Le type callable n'est connu qu'au moment de la construction et, par conséquent, std::function
ne peut pas avoir un membre pré-déclaré de ce type pour contenir une copie de l'objet donné à son constructeur.
En gros (en réalité, les choses sont plus compliquées que cela) std::function
ne peut contenir qu'un pointeur sur l'objet passé à son constructeur, ce qui pose un problème de durée de vie. Si le pointeur pointe vers un objet dont la durée de vie est inférieure à celle de l'objet std::function
alors le pointeur interne sera suspendu. Pour éviter ce problème std::function
pourrait faire une copie de l'objet sur le tas par le biais d'un appel à operator new
(ou un allocateur personnalisé). L'allocation dynamique de la mémoire est ce à quoi les gens font le plus référence comme étant une pénalité de performance impliquée par std::function
.
J'ai récemment écrit un article avec plus de détails et qui explique comment (et où) on peut éviter de payer le prix d'une allocation de mémoire.
Utilisation efficace des expressions lambda et de std::function
Vous pouvez trouver des informations dans les documents de référence du coup de pouce : Combien de frais généraux un appel à travers boost::function entraîne-t-il ? et Performance
Cela ne détermine pas un "oui ou un non" à la fonction de boost. La baisse de performance peut être tout à fait acceptable compte tenu des exigences du programme. Le plus souvent, certaines parties d'un programme ne sont pas critiques en termes de performances. Et même dans ce cas, elle peut être acceptable. C'est seulement quelque chose que vous pouvez déterminer.
Quant à la version de la bibliothèque standard, la norme ne définit qu'une interface. Il appartient entièrement aux implémentations individuelles de la faire fonctionner. Je suppose qu'une implémentation similaire à la fonction de Boost serait utilisée.
Tout d'abord, l'overhead diminue avec l'intérieur de la fonction ; plus la charge de travail est élevée, plus l'overhead est faible.
Deuxièmement : g++ 4.5 ne montre aucune différence par rapport aux fonctions virtuelles :
main.cc
#include <functional>
#include <iostream>
// Interface for virtual function test.
struct Virtual {
virtual ~Virtual() {}
virtual int operator() () const = 0;
};
// Factory functions to steal g++ the insight and prevent some optimizations.
Virtual *create_virt();
std::function<int ()> create_fun();
std::function<int ()> create_fun_with_state();
// The test. Generates actual output to prevent some optimizations.
template <typename T>
int test (T const& fun) {
int ret = 0;
for (int i=0; i<1024*1024*1024; ++i) {
ret += fun();
}
return ret;
}
// Executing the tests and outputting their values to prevent some optimizations.
int main () {
{
const clock_t start = clock();
std::cout << test(*create_virt()) << '\n';
const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
std::cout << "virtual: " << secs << " secs.\n";
}
{
const clock_t start = clock();
std::cout << test(create_fun()) << '\n';
const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
std::cout << "std::function: " << secs << " secs.\n";
}
{
const clock_t start = clock();
std::cout << test(create_fun_with_state()) << '\n';
const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
std::cout << "std::function with bindings: " << secs << " secs.\n";
}
}
impl.cc
#include <functional>
struct Virtual {
virtual ~Virtual() {}
virtual int operator() () const = 0;
};
struct Impl : Virtual {
virtual ~Impl() {}
virtual int operator() () const { return 1; }
};
Virtual *create_virt() { return new Impl; }
std::function<int ()> create_fun() {
return []() { return 1; };
}
std::function<int ()> create_fun_with_state() {
int x,y,z;
return [=]() { return 1; };
}
Sortie de g++ --std=c++0x -O3 impl.cc main.cc && ./a.out
:
1073741824
virtual: 2.9 secs.
1073741824
std::function: 2.9 secs.
1073741824
std::function with bindings: 2.9 secs.
Alors, n'ayez crainte. Si votre conception/maintenabilité peut s'améliorer en préférant std::function
sur les appels virtuels, essayez-les. Personnellement, j'aime beaucoup l'idée de ne pas imposer les interfaces et l'héritage aux clients de mes classes.
Cela dépend fortement si vous passez la fonction sans lier aucun argument (n'alloue pas d'espace de tas) ou non.
Cela dépend aussi d'autres facteurs, mais c'est le principal.
Il est vrai que vous avez besoin d'un point de comparaison, vous ne pouvez pas simplement dire que cela "réduit les frais généraux" par rapport au fait de ne pas l'utiliser du tout, vous devez le comparer à l'utilisation d'une autre façon de passer une fonction. Et si vous pouvez simplement vous dispenser de l'utiliser, alors il n'était pas nécessaire dès le départ.