Каковы издержки производительности std::function?
Я слышал на форуме, используя std::function<>
вызывает снижение производительности. Это правда? Если это правда, это большое падение производительности?
5 ответов
Вы можете найти информацию в справочных материалах boost: сколько накладных расходов вызывает вызов через boost::function? и производительность
Это не определяет "да или нет" для усиления функции. Падение производительности может быть вполне приемлемым, учитывая требования программы. Чаще всего части программы не являются критичными для производительности. И даже тогда это может быть приемлемым. Это только то, что вы можете определить.
Что касается стандартной версии библиотеки, стандарт определяет только интерфейс. Это полностью зависит от индивидуальных реализаций, чтобы заставить его работать. Я полагаю, что будет использована аналогичная реализация функции boost.
Есть действительно проблемы с производительностью std:function
это необходимо учитывать при его использовании. Основная сила std::function
а именно, его механизм стирания типов не предоставляется бесплатно, и мы могли бы (но не обязательно должны) заплатить за это цену.
std::function
это шаблонный класс, который оборачивает вызываемые типы. Однако он не параметризован для самого вызываемого типа, а только для его типов возврата и аргументов. Вызываемый тип известен только во время строительства и, следовательно, std::function
не может иметь предварительно объявленного члена этого типа для хранения копии объекта, переданного его конструктору.
Грубо говоря (на самом деле все сложнее, чем это) std::function
может содержать только указатель на объект, переданный его конструктору, и это вызывает проблему на всю жизнь. Если указатель указывает на объект, время жизни которого меньше, чем у std::function
объект, то внутренний указатель станет болтаться. Чтобы предотвратить эту проблему std::function
может сделать копию объекта в куче через вызов operator new
(или пользовательский распределитель). Динамическое распределение памяти - это то, что люди называют больше всего как снижение производительности, подразумеваемое 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<> / std :: function <> с 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.