提问人: 提问时间:2/20/2011 最后编辑:Craig M. Brandenburg 更新时间:9/20/2021 访问量:53415
std::function 的性能开销是多少?
What is the performance overhead of std::function?
答:
您可以从 boost 的参考资料中找到信息:通过 boost::function 调用会产生多少开销? 和性能
这并不能确定“是或否”来提升功能。考虑到程序的要求,性能下降可能是可以接受的。通常情况下,程序的某些部分对性能不重要。即便如此,它也可能是可以接受的。这只能由您决定。
至于标准库版本,该标准仅定义了一个接口。这完全取决于各个实现来使其工作。我想会使用与 boost 函数类似的实现。
这在很大程度上取决于您是否在不绑定任何参数(不分配堆空间)的情况下传递函数。
还取决于其他因素,但这是主要因素。
的确,你需要一些东西来比较,你不能简单地说,与根本不使用它相比,它“减少了开销”,你需要将它与使用传递函数的替代方式进行比较。如果你可以完全不使用它,那么从一开始就不需要它
评论
std::function
首先,随着函数内部的增加,开销变小;工作负载越高,开销越小。
其次:g++ 4.5 与虚函数相比没有任何区别:
main.cc
#include <functional>
#include <iostream>
// Interface for virtual function test.
struct Virtual {
virtual ~Virtual() {}
virtual int operator() () const = 0;
};
// Factory functions to steal g++ the insight and prevent some optimizations.
Virtual *create_virt();
std::function<int ()> create_fun();
std::function<int ()> create_fun_with_state();
// The test. Generates actual output to prevent some optimizations.
template <typename T>
int test (T const& fun) {
int ret = 0;
for (int i=0; i<1024*1024*1024; ++i) {
ret += fun();
}
return ret;
}
// Executing the tests and outputting their values to prevent some optimizations.
int main () {
{
const clock_t start = clock();
std::cout << test(*create_virt()) << '\n';
const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
std::cout << "virtual: " << secs << " secs.\n";
}
{
const clock_t start = clock();
std::cout << test(create_fun()) << '\n';
const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
std::cout << "std::function: " << secs << " secs.\n";
}
{
const clock_t start = clock();
std::cout << test(create_fun_with_state()) << '\n';
const double secs = (clock()-start) / double(CLOCKS_PER_SEC);
std::cout << "std::function with bindings: " << secs << " secs.\n";
}
}
impl.cc
#include <functional>
struct Virtual {
virtual ~Virtual() {}
virtual int operator() () const = 0;
};
struct Impl : Virtual {
virtual ~Impl() {}
virtual int operator() () const { return 1; }
};
Virtual *create_virt() { return new Impl; }
std::function<int ()> create_fun() {
return []() { return 1; };
}
std::function<int ()> create_fun_with_state() {
int x,y,z;
return [=]() { return 1; };
}
输出:g++ --std=c++0x -O3 impl.cc main.cc && ./a.out
1073741824
virtual: 2.9 secs.
1073741824
std::function: 2.9 secs.
1073741824
std::function with bindings: 2.9 secs.
所以,不要害怕。如果您的设计/可维护性可以通过首选虚拟呼叫来提高,请尝试一下。就我个人而言,我真的很喜欢不在我的类的客户端上强制接口和继承的想法。std::function
评论
std::function
-flto
std::function
-flto
事实上,在使用它时必须考虑性能问题。的主要优势,即它的类型擦除机制,不是免费的,我们可能(但不一定必须)为此付出代价。std:function
std::function
std::function
是包装可调用类型的模板类。但是,它不会对可调用类型本身进行参数化,而只会对它的返回和参数类型进行参数化。可调用类型仅在构造时是已知的,因此,不能有此类型的预先声明的成员来保存给定给其构造函数的对象的副本。std::function
粗略地说(实际上,事情比这更复杂)只能包含指向传递给其构造函数的对象的指针,这引发了一个生存期问题。如果指针指向的对象的生存期小于该对象的生存期,则内部指针将悬空。若要防止此问题,可以通过调用(或自定义分配器)在堆上创建对象的副本。动态内存分配是人们最常提到的性能损失。std::function
std::function
std::function
operator new
std::function
我最近写了一篇文章,详细介绍了如何(以及在哪里)避免支付内存分配的代价。
高效使用 Lambda 表达式和 std::function
评论
boost::function
对调用性能进行了说明:“使用正确的内联编译器,函数对象的调用需要通过函数指针进行一次调用。如果调用是自由函数指针,则必须对该函数指针进行额外的调用(除非编译器具有非常强大的过程间分析)。std::function
std::function<> / std::function<> with bind( ... ) 非常快。检查一下:
#include <iostream>
#include <functional>
#include <chrono>
using namespace std;
using namespace chrono;
int main()
{
static size_t const ROUNDS = 1'000'000'000;
static
auto bench = []<typename Fn>( Fn const &fn ) -> double
{
auto start = high_resolution_clock::now();
fn();
return (int64_t)duration_cast<nanoseconds>( high_resolution_clock::now() - start ).count() / (double)ROUNDS;
};
int i;
static
auto CLambda = []( int &i, int j )
{
i += j;
};
auto bCFn = [&]() -> double
{
void (*volatile pFnLambda)( int &i, int j ) = CLambda;
return bench( [&]()
{
for( size_t j = ROUNDS; j--; j )
pFnLambda( i, 2 );
} );
};
auto bndObj = bind( CLambda, ref( i ), 2 );
auto bBndObj = [&]() -> double
{
decltype(bndObj) *volatile pBndObj = &bndObj;
return bench( [&]()
{
for( size_t j = ROUNDS; j--; j )
(*pBndObj)();
} );
};
using fn_t = function<void()>;
auto bFnBndObj = [&]() -> double
{
fn_t fnBndObj = fn_t( bndObj );
fn_t *volatile pFnBndObj = &fnBndObj;
return bench( [&]()
{
for( size_t j = ROUNDS; j--; j )
(*pFnBndObj)();
} );
};
auto bFnBndObjCap = [&]() -> double
{
auto capLambda = [&i]( int j )
{
i += j;
};
fn_t fnBndObjCap = fn_t( bind( capLambda, 2 ) );
fn_t *volatile pFnBndObjCap = &fnBndObjCap;
return bench( [&]()
{
for( size_t j = ROUNDS; j--; j )
(*pFnBndObjCap)();
} );
};
using bench_fn = function<double()>;
static const
struct descr_bench
{
char const *descr;
bench_fn const fn;
} dbs[] =
{
{ "C-function",
bench_fn( bind( bCFn ) ) },
{ "C-function in bind( ... ) with all parameters",
bench_fn( bind( bBndObj ) ) },
{ "C-function in function<>( bind( ... ) ) with all parameters",
bench_fn( bind( bFnBndObj ) ) },
{ "lambda capturiging first parameter in function<>( bind( lambda, 2 ) )",
bench_fn( bind( bFnBndObjCap ) ) }
};
for( descr_bench const &db : dbs )
cout << db.descr << ":" << endl,
cout << db.fn() << endl;
}
在我的计算机上,所有呼叫都低于 2ns。
评论