我在一个论坛上听说使用 std::function<>
会导致性能下降。这是真的吗?如果是,那么性能下降很大吗?
我在一个论坛上听说使用 std::function<>
会导致性能下降。这是真的吗?如果是,那么性能下降很大吗?
在使用std::function
时,确实存在性能问题需要考虑。 std::function
的主要优势是其类型擦除机制,但这不是免费的,我们可能(但不一定)需要为此付出代价。
std::function
是一个模板类,用于封装可调用类型。然而,它不是根据可调用类型本身进行参数化的,而仅仅是基于其返回和参数类型。 可调用类型仅在构造函数时才知道,因此,std::function
无法有一个预先声明的成员来保存给定给其构造函数的对象的副本。
粗略地说(实际上,情况比这更复杂),std::function
只能持有指向传递给其构造函数的对象的指针,这会引发生命周期问题。如果指针指向的对象的生命周期小于 std::function
对象的生命周期,则内部指针将变得悬空。为了防止这个问题,std::function
可能通过调用 operator new
(或自定义分配器)在堆上复制对象。人们最常提到的性能惩罚是动态内存分配。
我最近撰写了一篇文章,详细介绍了如何(以及在哪里)避免支付内存分配的代价。
std::function
的开销。boost::function
关于调用性能的说明如下:“使用适当的内联编译器,函数对象的调用只需通过一个函数指针进行一次调用。如果调用是由于自由函数指针引起的,则还必须对该函数指针进行其他调用(除非编译器具有非常强大的过程间分析)。” - mucaho#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
可以很容易地通过虚函数实现。然而,大多数实现似乎使用函数指针来模板化函数和一个void *
指针。间接性实际上是相同的。 - Xeostd::function
相比有1:3的差异,因此这个测试并不完全没有道理。 - Sebastian Machstd::function
(类似于使用虚函数得到的-flto
),但这些优化实际上会产生负面影响。 - MWBg++ --std=c++17 -O3 impl.cc main.cc && ./a.out
。虚函数(virtual)用时 1.38742 秒,std::function 用时 1.44681 秒,带绑定的 std::function 用时 1.39367 秒。 - Andrej Kesely这很大程度上取决于您是否在不绑定任何参数的情况下传递函数(不分配堆空间)。
还取决于其他因素,但这是主要因素。
确实需要有东西来进行比较,不能仅仅说它“减少了开销”而不与使用其他传递函数的替代方式进行比较。如果完全可以不使用它,则从一开始就不需要它。
std::function
实例中,并且传递的可调用对象大小适合SBO,则即使绑定参数也可能不会产生动态分配。 - underscore_d使用bind(...)的std::function<> / std::function<>非常快。请查看以下内容:
#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;
}
在我的电脑上,所有的调用都在2纳秒以下。