Questa risposta intende contribuire, all'insieme delle risposte esistenti, a quello che ritengo sia un punto di riferimento più significativo per il costo di runtime delle chiamate std :: function.
Il meccanismo std :: function dovrebbe essere riconosciuto per ciò che fornisce: Qualsiasi entità richiamabile può essere convertita in una funzione std :: della firma appropriata. Supponiamo di avere una libreria che si adatta a una superficie a una funzione definita da z = f (x, y), puoi scriverla per accettare a std::function<double(double,double)>
e l'utente della libreria può facilmente convertire qualsiasi entità chiamabile in quella; sia una normale funzione, un metodo di un'istanza di classe, o un lambda, o qualsiasi cosa sia supportata da std :: bind.
A differenza degli approcci modello, questo funziona senza dover ricompilare la funzione di libreria per diversi casi; di conseguenza, per ogni caso aggiuntivo è necessario un piccolo codice compilato. È sempre stato possibile farlo accadere, ma richiedeva alcuni meccanismi imbarazzanti e l'utente della biblioteca avrebbe probabilmente bisogno di costruire un adattatore attorno alla sua funzione per farlo funzionare. std :: function costruisce automaticamente qualunque adattatore sia necessario per ottenere un'interfaccia di chiamata di runtime comune per tutti i casi, che è una funzionalità nuova e molto potente.
A mio avviso, questo è il caso d'uso più importante per std :: function per quanto riguarda le prestazioni: sono interessato al costo di chiamare una std :: function molte volte dopo che è stata costruita una volta, e deve essere una situazione in cui il compilatore non è in grado di ottimizzare la chiamata conoscendo la funzione effettivamente chiamata (cioè è necessario nascondere l'implementazione in un altro file sorgente per ottenere un benchmark adeguato).
Ho fatto il test di seguito, simile ai PO; ma i principali cambiamenti sono:
- Ogni caso viene ripetuto 1 miliardo di volte, ma gli oggetti std :: function vengono costruiti una sola volta. Ho scoperto guardando il codice di output che viene chiamato 'operatore nuovo' quando si costruiscono chiamate std :: function (forse non quando sono ottimizzate).
- Il test è diviso in due file per impedire l'ottimizzazione indesiderata
- I miei casi sono: (a) la funzione è in linea (b) la funzione è passata da un normale puntatore a funzione (c) la funzione è una funzione compatibile avvolta come std :: funzione (d) è una funzione incompatibile resa compatibile con una std :: bind, avvolto come std :: function
I risultati che ottengo sono:
Il caso (d) tende ad essere leggermente più lento, ma la differenza (circa 0,05 nsec) viene assorbita dal rumore.
La conclusione è che la funzione std :: è un sovraccarico paragonabile (al momento della chiamata) all'utilizzo di un puntatore a funzione, anche quando c'è un semplice adattamento 'bind' alla funzione reale. L'Inline è 2 ns più veloce degli altri, ma questo è un compromesso atteso poiché l'Inline è l'unico caso che è "cablato" in fase di esecuzione.
Quando eseguo il codice di johan-lundberg sulla stessa macchina, vedo circa 39 nsec per loop, ma c'è molto di più nel loop lì, incluso l'attuale costruttore e distruttore della funzione std ::, che è probabilmente abbastanza alto poiché comporta un nuovo ed elimina.
-O2 gcc 4.8.1, al target x86_64 (core i5).
Nota, il codice è suddiviso in due file, per impedire al compilatore di espandere le funzioni in cui sono chiamati (tranne nel caso in cui è previsto).
----- primo file sorgente --------------
#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;
}
----- secondo file sorgente -------------
#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;
}
Per coloro che sono interessati, ecco l'adattatore creato dal compilatore per far sembrare 'mul_by' un float (float) - questo viene chiamato quando viene chiamata la funzione creata come bind (mul_by, _1,0.5):
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
(quindi potrebbe essere stato un po 'più veloce se avessi scritto 0,5f nel bind ...) Nota che il parametro' x 'arriva in% xmm0 e rimane lì.
Ecco il codice nell'area in cui è costruita la funzione, prima di chiamare test_stdfunc - eseguire 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 solo se in realtà è necessaria una raccolta eterogenea di oggetti richiamabili (ovvero non sono disponibili ulteriori informazioni discriminanti in fase di esecuzione).