Esta resposta pretende contribuir, para o conjunto de respostas existentes, o que eu acredito ser uma referência mais significativa para o custo de tempo de execução das chamadas std :: function.
O mecanismo std :: function deve ser reconhecido pelo que fornece: Qualquer entidade que possa ser chamada pode ser convertida em uma função std :: de assinatura apropriada. Suponha que você tenha uma biblioteca que ajuste uma superfície a uma função definida por z = f (x, y), você pode escrevê-la para aceitar a std::function<double(double,double)>
e o usuário da biblioteca pode converter facilmente qualquer entidade que possa ser chamada; seja uma função comum, um método de uma instância de classe ou um lambda, ou qualquer coisa suportada pelo std :: bind.
Diferentemente das abordagens de modelo, isso funciona sem a necessidade de recompilar a função de biblioteca para diferentes casos; consequentemente, pouco código compilado extra é necessário para cada caso adicional. Sempre foi possível fazer isso acontecer, mas costumava exigir alguns mecanismos estranhos, e o usuário da biblioteca provavelmente precisaria construir um adaptador em torno de sua função para fazê-la funcionar. A função std :: constrói automaticamente qualquer adaptador necessário para obter uma interface de chamada de tempo de execução comum para todos os casos, que é um recurso novo e muito poderoso.
Na minha opinião, este é o caso de uso mais importante para std :: function no que diz respeito ao desempenho: estou interessado no custo de chamar uma função std :: muitas vezes depois de ter sido construída uma vez e precisa pode ser uma situação em que o compilador não consegue otimizar a chamada conhecendo a função que está sendo chamada (ou seja, você precisa ocultar a implementação em outro arquivo de origem para obter uma referência adequada).
Fiz o teste abaixo, semelhante ao OP; mas as principais mudanças são:
- Cada caso faz um loop de 1 bilhão de vezes, mas os objetos std :: function são construídos apenas uma vez. Descobri olhando para o código de saída que 'operador novo' é chamado ao construir chamadas std :: function reais (talvez não quando elas são otimizadas).
- O teste é dividido em dois arquivos para evitar otimização indesejada
- Meus casos são: (a) a função é inline (b) a função é passada por um ponteiro de função comum (c) a função é uma função compatível agrupada como std :: function (d) a função é uma função incompatível compatível com um std :: bind, envolvido como std :: function
Os resultados obtidos são:
case (a) (inline) 1,3 nsec
todos os outros casos: 3,3 nsec.
O caso (d) tende a ser um pouco mais lento, mas a diferença (cerca de 0,05 ns) é absorvida pelo ruído.
A conclusão é que a função std :: é uma sobrecarga comparável (no momento da chamada) ao uso de um ponteiro de função, mesmo quando há uma adaptação simples de 'ligação' à função real. O inline é 2 ns mais rápido que os outros, mas é uma troca esperada, pois o inline é o único caso que é 'conectado por fio' no tempo de execução.
Quando executo o código de johan-lundberg na mesma máquina, vejo cerca de 39 nsec por loop, mas há muito mais no loop, incluindo o construtor e destruidor da função std ::, que provavelmente é bastante alta uma vez que envolve um novo e excluir.
-O2 gcc 4.8.1, para o destino x86_64 (core i5).
Observe que o código é dividido em dois arquivos, para impedir que o compilador expanda as funções onde são chamadas (exceto no caso em que se destina).
----- primeiro arquivo de origem --------------
#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;
}
----- segundo arquivo de origem -------------
#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;
}
Para os interessados, aqui está o adaptador que o compilador construiu para fazer 'mul_by' parecer um float (float) - isso é 'chamado' quando a função criada como bind (mul_by, _1,0.5) é chamada:
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
(portanto, poderia ter sido um pouco mais rápido se eu tivesse escrito 0,5f no bind ...) Observe que o parâmetro 'x' chega em% xmm0 e permanece lá.
Aqui está o código na área em que a função é construída, antes de chamar test_stdfunc - execute o 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
se e somente se você realmente precisar de uma coleção heterogênea de objetos que podem ser chamados (ou seja, nenhuma informação discriminante adicional está disponível no tempo de execução).