Jawaban ini dimaksudkan untuk berkontribusi, ke set jawaban yang ada, apa yang saya yakini sebagai tolok ukur yang lebih bermakna untuk biaya runtime panggilan std :: function.
Mekanisme std :: function harus dikenali dari apa yang disediakannya: Entitas apa pun yang dapat dipanggil dapat dikonversi ke fungsi std :: function dari tanda tangan yang sesuai. Misalkan Anda memiliki pustaka yang sesuai dengan permukaan ke fungsi yang didefinisikan oleh z = f (x, y), Anda dapat menulisnya untuk menerima a std::function<double(double,double)>
, dan pengguna perpustakaan dapat dengan mudah mengkonversi entitas yang dapat dipanggil apa pun menjadi itu; baik itu fungsi biasa, metode instance kelas, atau lambda, atau apa pun yang didukung oleh std :: bind.
Tidak seperti pendekatan templat, ini bekerja tanpa harus mengkompilasi ulang fungsi pustaka untuk berbagai kasus; oleh karena itu, sedikit kode terkompilasi ekstra diperlukan untuk setiap kasus tambahan. Itu selalu mungkin untuk membuat ini terjadi, tetapi dulu memerlukan beberapa mekanisme canggung, dan pengguna perpustakaan mungkin perlu membangun adaptor di sekitar fungsi mereka untuk membuatnya berfungsi. std :: function secara otomatis membuat adaptor apa pun yang diperlukan untuk mendapatkan antarmuka panggilan runtime yang umum untuk semua kasus, yang merupakan fitur baru dan sangat kuat.
Menurut pendapat saya, ini adalah use case yang paling penting untuk fungsi std :: sejauh menyangkut kinerja: Saya tertarik pada biaya memanggil fungsi std :: berkali-kali setelah dibangun satu kali, dan perlu menjadi situasi di mana kompiler tidak dapat mengoptimalkan panggilan dengan mengetahui fungsi yang sebenarnya dipanggil (yaitu Anda harus menyembunyikan implementasi di file sumber lain untuk mendapatkan patokan yang tepat).
Saya membuat tes di bawah ini, mirip dengan OP; tetapi perubahan utamanya adalah:
- Setiap case loop 1 miliar kali, tetapi objek std :: function dibangun hanya sekali. Saya telah menemukan dengan melihat kode keluaran yang disebut 'operator baru' saat membuat panggilan fungsi std :: function aktual (mungkin tidak ketika dioptimalkan).
- Tes dibagi menjadi dua file untuk mencegah optimasi yang tidak diinginkan
- Kasus saya adalah: (a) fungsi digarisbawahi (b) fungsi dilewatkan oleh fungsi pointer biasa (c) fungsi adalah fungsi yang kompatibel dibungkus sebagai std :: function (d) fungsi adalah fungsi yang tidak kompatibel dibuat kompatibel dengan std :: bind, dibungkus sebagai std :: function
Hasil yang saya dapatkan adalah:
Kasus (d) cenderung sedikit lebih lambat, tetapi perbedaannya (sekitar 0,05 nsec) diserap dalam kebisingan.
Kesimpulannya adalah bahwa fungsi std :: adalah overhead yang sebanding (pada waktu panggilan) untuk menggunakan pointer fungsi, bahkan ketika ada adaptasi 'bind' sederhana dengan fungsi sebenarnya. Inline adalah 2 ns lebih cepat daripada yang lain tapi itu tradeoff yang diharapkan karena inline adalah satu-satunya kasus yang 'terprogram' pada saat run time.
Ketika saya menjalankan kode johan-lundberg pada mesin yang sama, saya melihat sekitar 39 nsec per loop, tetapi ada lebih banyak di loop di sana, termasuk konstruktor dan destruktor sebenarnya dari fungsi std :: function, yang mungkin cukup tinggi karena itu melibatkan yang baru dan hapus.
-O2 gcc 4.8.1, untuk target x86_64 (core i5).
Catatan, kode ini dipecah menjadi dua file, untuk mencegah kompiler memperluas fungsi di mana mereka dipanggil (kecuali dalam satu kasus di mana itu dimaksudkan untuk).
----- file sumber pertama --------------
#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;
}
----- file sumber kedua -------------
#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;
}
Bagi mereka yang tertarik, inilah adaptor yang dibuat kompiler untuk membuat 'mul_by' terlihat seperti float (float) - ini disebut 'ketika fungsi yang dibuat sebagai bind (mul_by, _1.0.5) dipanggil:
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
(jadi mungkin sedikit lebih cepat jika saya menulis 0,5f di bind ...) Perhatikan bahwa parameter 'x' tiba di% xmm0 dan tetap di sana.
Berikut kode di area di mana fungsi dibangun, sebelum memanggil test_stdfunc - jalankan melalui 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
jika dan hanya jika Anda benar - benar membutuhkan koleksi objek yang dapat dipanggil yang heterogen (yaitu tidak ada informasi diskriminatif lebih lanjut tersedia saat runtime).