この回答は、一連の既存の回答に、std :: function呼び出しの実行時コストのより意味のあるベンチマークであると私が考えるものを提供することを目的としています。
std :: functionメカニズムは、それが提供するものについて認識されるべきです:呼び出し可能なエンティティは、適切な署名のstd :: functionに変換できます。z = f(x、y)で定義された関数に面を適合させるライブラリーがあり、それを受け入れてを受け入れるstd::function<double(double,double)>
ことができ、ライブラリーのユーザーが呼び出し可能なエンティティーをそれに簡単に変換できると想定します。通常の関数でも、クラスインスタンスのメソッドでも、ラムダでも、std :: bindでサポートされているものでもかまいません。
テンプレートアプローチとは異なり、これはさまざまなケースでライブラリ関数を再コンパイルする必要なく機能します。したがって、追加のケースごとに追加のコンパイル済みコードはほとんど必要ありません。これを実現することは常に可能でしたが、以前は厄介なメカニズムが必要でした。また、ライブラリのユーザーは、機能するように関数の周りにアダプターを構築する必要がある可能性があります。std :: functionは、すべての場合に共通のランタイム呼び出しインターフェースを取得するために必要なアダプターを自動的に構築します。これは、新しい非常に強力な機能です。
私の見解では、パフォーマンスに関する限り、これはstd :: functionの最も重要な使用例です。一度構築された後にstd :: functionを何度も呼び出すコストに興味があり、コンパイラが実際に呼び出されている関数を知ることによって呼び出しを最適化できない状況である(つまり、適切なベンチマークを取得するには、別のソースファイルで実装を非表示にする必要がある)。
OPと同様に、以下のテストを行いました。主な変更点は次のとおりです。
- 各ケースは10億回ループしますが、std :: functionオブジェクトは1回だけ構築されます。実際のstd :: function呼び出しを作成するときに「operator new」が呼び出されるという出力コードを調べたところ、(最適化されていない場合はそうではない可能性があります)わかりました。
- テストは2つのファイルに分割され、不要な最適化を防止します
- (a)関数がインライン化されている(b)関数が通常の関数ポインタによって渡されている(c)関数がstd :: functionとしてラップされている互換関数である(d)関数が非互換関数であり、std ::と互換性があるバインド、std :: functionとしてラップ
私が得る結果は:
ケース(a)(インライン)1.3ナノ秒
その他すべての場合:3.3ナノ秒。
ケース(d)は少し遅くなる傾向がありますが、その差(約0.05ナノ秒)はノイズに吸収されます。
結論は、std :: functionは、実際の関数への単純な「バインド」適応がある場合でも、(呼び出し時の)関数ポインターの使用に匹敵するオーバーヘッドであることです。インラインは他のものより2 ns高速ですが、インラインが実行時に「ハードワイヤード」される唯一のケースであるため、これは予想されるトレードオフです。
同じマシンでjohan-lundbergのコードを実行すると、ループごとに約39ナノ秒が表示されますが、実際には、かなり高いstd :: functionの実際のコンストラクターとデストラクターを含めて、ループ内にはさらに多くのループがあります。新しい削除を伴うため。
-O2 gcc 4.8.1、x86_64ターゲット(コアi5)。
コードが2つのファイルに分割されていることに注意してください。これは、コンパイラーがそれらを呼び出す場所で関数を展開しないようにするためです(意図した場合を除きます)。
-----最初のソースファイル--------------
#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;
}
----- 2番目のソースファイル-------------
#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;
}
興味のある方のために、「mul_by」をfloat(float)のように見せるためにコンパイラが作成したアダプタを示します。これは、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
(したがって、バインドで0.5fを記述した場合は少し高速だったかもしれません...) 'x'パラメータは%xmm0に到着し、そこに留まることに注意してください。
test_stdfuncを呼び出す前の、関数が作成された領域のコードを次に示します-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
実際に必要な場合にのみ使用してください(つまり、実行時に識別情報がそれ以上利用できません)。