Cette réponse a pour but de contribuer, à l'ensemble des réponses existantes, à ce que je crois être une référence plus significative pour le coût d'exécution des appels std :: function.
Le mécanisme std :: function doit être reconnu pour ce qu'il fournit: toute entité appelable peut être convertie en une fonction std :: de signature appropriée. Supposons que vous ayez une bibliothèque qui ajuste une surface à une fonction définie par z = f (x, y), vous pouvez l'écrire pour accepter a std::function<double(double,double)>
, et l'utilisateur de la bibliothèque peut facilement convertir n'importe quelle entité appelable en cela; que ce soit une fonction ordinaire, une méthode d'une instance de classe, ou un lambda, ou tout ce qui est pris en charge par std :: bind.
Contrairement aux approches de modèle, cela fonctionne sans avoir à recompiler la fonction de bibliothèque pour différents cas; en conséquence, peu de code compilé supplémentaire est nécessaire pour chaque cas supplémentaire. Cela a toujours été possible, mais cela nécessitait des mécanismes maladroits, et l'utilisateur de la bibliothèque aurait probablement besoin de construire un adaptateur autour de sa fonction pour la faire fonctionner. std :: function construit automatiquement l'adaptateur nécessaire pour obtenir une interface d'appel d' exécution commune pour tous les cas, ce qui est une fonctionnalité nouvelle et très puissante.
À mon avis, c'est le cas d'utilisation le plus important de std :: function en ce qui concerne les performances: je suis intéressé par le coût d'appeler une fonction std :: function plusieurs fois après qu'elle a été construite une fois, et elle doit être une situation où le compilateur est incapable d'optimiser l'appel en connaissant la fonction réellement appelée (c'est-à-dire que vous devez cacher l'implémentation dans un autre fichier source pour obtenir un benchmark approprié).
J'ai fait le test ci-dessous, similaire aux OP; mais les principaux changements sont:
- Chaque cas boucle 1 milliard de fois, mais les objets std :: function ne sont construits qu'une seule fois. J'ai trouvé en regardant le code de sortie que 'operator new' est appelé lors de la construction d'appels std :: function réels (peut-être pas lorsqu'ils sont optimisés).
- Le test est divisé en deux fichiers pour éviter une optimisation indésirable
- Mes cas sont: (a) la fonction est en ligne (b) la fonction est passée par un pointeur de fonction ordinaire (c) la fonction est une fonction compatible enveloppée comme std :: function (d) function est une fonction incompatible rendue compatible avec un std :: bind, enveloppé comme std :: function
Les résultats que j'obtiens sont:
cas (a) (en ligne) 1,3 nsec
tous les autres cas: 3,3 nsec.
Le cas (d) a tendance à être légèrement plus lent, mais la différence (environ 0,05 nsec) est absorbée dans le bruit.
La conclusion est que la fonction std :: est comparable (au moment de l'appel) à l'utilisation d'un pointeur de fonction, même lorsqu'il y a une simple adaptation 'bind' à la fonction réelle. Le inline est 2 ns plus rapide que les autres, mais c'est un compromis attendu puisque le inline est le seul cas qui soit «câblé» au moment de l'exécution.
Lorsque j'exécute le code de johan-lundberg sur la même machine, je vois environ 39 nsec par boucle, mais il y en a beaucoup plus dans la boucle, y compris le constructeur et le destructeur réels de la fonction std ::, qui est probablement assez élevé car il s'agit d'un nouveau et supprimer.
-O2 gcc 4.8.1, vers la cible x86_64 (core i5).
Notez que le code est divisé en deux fichiers, pour empêcher le compilateur d'étendre les fonctions là où elles sont appelées (sauf dans le cas où il est prévu).
----- premier fichier source --------------
#include <functional>
// simple funct
float func_half( float x ) { return x * 0.5; }
// func we can bind
float mul_by( float x, float scale ) { return x * scale; }
//
// func to call another func a zillion times.
//
float test_stdfunc( std::function<float(float)> const & func, int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func(x);
}
return y;
}
// same thing with a function pointer
float test_funcptr( float (*func)(float), int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func(x);
}
return y;
}
// same thing with inline function
float test_inline( int nloops ) {
float x = 1.0;
float y = 0.0;
for(int i =0; i < nloops; i++ ){
y += x;
x = func_half(x);
}
return y;
}
----- deuxième fichier source -------------
#include <iostream>
#include <functional>
#include <chrono>
extern float func_half( float x );
extern float mul_by( float x, float scale );
extern float test_inline( int nloops );
extern float test_stdfunc( std::function<float(float)> const & func, int nloops );
extern float test_funcptr( float (*func)(float), int nloops );
int main() {
using namespace std::chrono;
for(int icase = 0; icase < 4; icase ++ ){
const auto tp1 = system_clock::now();
float result;
switch( icase ){
case 0:
result = test_inline( 1e9);
break;
case 1:
result = test_funcptr( func_half, 1e9);
break;
case 2:
result = test_stdfunc( func_half, 1e9);
break;
case 3:
result = test_stdfunc( std::bind( mul_by, std::placeholders::_1, 0.5), 1e9);
break;
}
const auto tp2 = high_resolution_clock::now();
const auto d = duration_cast<milliseconds>(tp2 - tp1);
std::cout << d.count() << std::endl;
std::cout << result<< std::endl;
}
return 0;
}
Pour ceux qui sont intéressés, voici l'adaptateur que le compilateur a construit pour que 'mul_by' ressemble à un float (float) - il est 'appelé' lorsque la fonction créée en tant que bind (mul_by, _1,0.5) est appelée:
movq (%rdi), %rax ; get the std::func data
movsd 8(%rax), %xmm1 ; get the bound value (0.5)
movq (%rax), %rdx ; get the function to call (mul_by)
cvtpd2ps %xmm1, %xmm1 ; convert 0.5 to 0.5f
jmp *%rdx ; jump to the func
(donc ça aurait pu être un peu plus rapide si j'avais écrit 0.5f dans la liaison ...) Notez que le paramètre 'x' arrive dans% xmm0 et y reste juste.
Voici le code dans la zone où la fonction est construite, avant d'appeler test_stdfunc - exécutez via c ++ filt:
movl $16, %edi
movq $0, 32(%rsp)
call operator new(unsigned long) ; get 16 bytes for std::function
movsd .LC0(%rip), %xmm1 ; get 0.5
leaq 16(%rsp), %rdi ; (1st parm to test_stdfunc)
movq mul_by(float, float), (%rax) ; store &mul_by in std::function
movl $1000000000, %esi ; (2nd parm to test_stdfunc)
movsd %xmm1, 8(%rax) ; store 0.5 in std::function
movq %rax, 16(%rsp) ; save ptr to allocated mem
;; the next two ops store pointers to generated code related to the std::function.
;; the first one points to the adaptor I showed above.
movq std::_Function_handler<float (float), std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_invoke(std::_Any_data const&, float), 40(%rsp)
movq std::_Function_base::_Base_manager<std::_Bind<float (*(std::_Placeholder<1>, double))(float, float)> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation), 32(%rsp)
call test_stdfunc(std::function<float (float)> const&, int)
std::function
si et seulement si vous avez réellement besoin d' une collection hétérogène d'objets appelables (c'est-à-dire qu'aucune autre information discriminante n'est disponible à l'exécution).