该答案旨在为现有答案集做出贡献,我认为这对于std :: function调用的运行时成本而言是更有意义的基准。
应该根据其提供的功能来识别std :: function机制:任何可调用的实体都可以转换为具有适当签名的std :: function。假设您有一个使表面适合z = f(x,y)定义的函数的库,可以将其编写为接受a std::function<double(double,double)>
,并且该库的用户可以轻松地将任何可调用实体转换为该函数;它是普通函数,类实例的方法,lambda还是std :: bind支持的任何东西。
与模板方法不同,此方法无需为不同情况重新编译库函数。因此,每种其他情况几乎不需要额外的编译代码。总是有可能做到这一点,但是它曾经需要一些笨拙的机制,并且库的用户可能需要围绕它们的功能构造一个适配器以使其起作用。std :: function会自动构造所需的任何适配器,以在所有情况下获得通用的运行时调用接口,这是一项非常强大的新功能。
我认为,就性能而言,这是std :: function的最重要用例:我对一次构造一次std :: function的代价很感兴趣,因此需要多次调用它。可能是编译器无法通过了解实际被调用的函数来优化调用的情况(即,您需要将实现隐藏在另一个源文件中以获得适当的基准)。
我在下面进行了测试,类似于OP的测试;但主要变化是:
- 每个案例循环10亿次,但std :: function对象仅构造一次。通过查看输出代码,我发现在构造实际的std :: function调用时调用了“ operator new”(也许在优化时未调用)。
- 将测试分为两个文件,以防止不必要的优化
- 我的情况是:(a)内联函数(b)普通函数指针传递函数(c)函数是包装为std :: function的兼容函数(d)函数是与std ::兼容的不兼容函数绑定,包装为std :: function
我得到的结果是:
情况(a)(内联)1.3 ns
其他所有情况:3.3纳秒。
情况(d)趋于稍微慢一些,但是差异(大约0.05纳秒)被噪声吸收了。
结论是,即使对实际函数进行简单的“绑定”适配,std :: function的开销(在调用时)也可以与使用函数指针相比。内联比其他的快2 ns,但这是一个预期的折衷,因为内联是唯一在运行时“硬连线”的情况。
当我在同一台机器上运行johan-lundberg的代码时,我看到每个循环大约39纳秒,但循环中还有很多东西,包括std :: function的实际构造函数和析构函数,这可能相当高因为它涉及一个新的和删除的。
-O2 gcc 4.8.1,到x86_64目标(核心i5)。
请注意,代码被分成两个文件,以防止编译器在调用它们的地方扩展功能(在一种情况下除外)。
-----第一个源文件--------------
#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;
}
-----第二个源文件-------------
#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
仅当您确实需要异构的可调用对象集合时才使用(即,在运行时没有其他区分信息可用)。