Этот ответ призван внести вклад в набор существующих ответов, что я считаю более значимым эталоном стоимости выполнения вызовов std :: function.
Механизм std :: function следует распознать по тому, что он предоставляет: любой вызываемый объект может быть преобразован в std :: function соответствующей сигнатуры. Предположим, у вас есть библиотека, которая соответствует поверхности функции, определенной как z = f (x, y), вы можете написать ее так, чтобы она принимала std::function<double(double,double)>
, и пользователь библиотеки может легко преобразовать в нее любую вызываемую сущность; будь то обычная функция, метод экземпляра класса или лямбда, или что-то еще, что поддерживается std :: bind.
В отличие от шаблонных подходов, это работает без необходимости перекомпилировать библиотечную функцию для разных случаев; соответственно, для каждого дополнительного случая требуется немного дополнительного скомпилированного кода. Это всегда было возможно, но раньше для этого требовались некоторые неудобные механизмы, и пользователю библиотеки, вероятно, нужно было создать адаптер для своей функции, чтобы заставить ее работать. std :: function автоматически создает любой адаптер, необходимый для получения общего интерфейса вызова времени выполнения для всех случаев, что является новой и очень мощной функцией.
На мой взгляд, это наиболее важный вариант использования std :: function с точки зрения производительности: меня интересует стоимость вызова std :: function много раз после того, как она была построена один раз, и ей необходимо быть ситуацией, когда компилятор не может оптимизировать вызов, зная, какая функция действительно вызывается (т.е. вам нужно скрыть реализацию в другом исходном файле, чтобы получить надлежащий тест).
Я сделал тест ниже, похожий на OP; но основные изменения:
- Каждый case повторяется 1 миллиард раз, но объекты std :: function создаются только один раз. Посмотрев на выходной код, я обнаружил, что оператор new вызывается при создании фактических вызовов std :: function (возможно, не тогда, когда они оптимизированы).
- Тест разделен на два файла, чтобы предотвратить нежелательную оптимизацию.
- Мои случаи: (a) функция встроена (b) функция передается обычным указателем на функцию (c) функция является совместимой функцией, завернутой как std :: function (d) функция является несовместимой функцией, совместимой с std :: привязать, завернутый как std :: function
Я получаю следующие результаты:
case (a) (встроенный) 1,3 нс
во всех остальных случаях: 3,3 нс.
Случай (d) имеет тенденцию быть немного медленнее, но разница (около 0,05 нс) поглощается шумом.
Вывод состоит в том, что std :: function накладные расходы (во время вызова) сопоставимы с использованием указателя функции, даже если существует простая адаптация «привязки» к фактической функции. Встроенный на 2 нс быстрее, чем другие, но это ожидаемый компромисс, поскольку встроенный - единственный случай, который «жестко закреплен» во время выполнения.
Когда я запускаю код johan-lundberg на той же машине, я вижу около 39 нс на цикл, но там гораздо больше в цикле, включая фактический конструктор и деструктор std :: function, который, вероятно, довольно высок. так как он предполагает создание и удаление.
-O2 gcc 4.8.1, для цели x86_64 (core 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 ++ filter:
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)
person
greggo
schedule
05.03.2014
std::function
тогда и только тогда, когда вам действительно нужна разнородная коллекция вызываемых объектов (т. Е. Во время выполнения не доступна никакая дополнительная различающая информация). - person Kerrek SB   schedule 04.02.2013std::function
или шаблоны. Я думаю, что здесь проблема заключается в том, чтобы просто заключить лямбду вstd::function
, а не вstd::function
. На данный момент ваш вопрос подобен тому, что я предпочитаю яблоко или миску? - person Lightness Races in Orbit   schedule 04.02.2013boost::function
, это просто занимает на 100% больше времени, чем версия шаблона (в 2 раза) (с GCC. Clang взял 0ns для версии шаблона. Похоже, он оптимизировал его). Я подозреваю, что вам следует указать реализацию, которую вы используете в тесте. - person Johannes Schaub - litb   schedule 04.02.2013std::sort
. В таких случаях я предпочитаю шаблоны (которые в любом случае в основном используются). Но в других случаях это действительно не имеет значения, - person ipc   schedule 04.02.2013std::function
, когда он вам нужен. Куча недостатков простых аргументов функтора шаблона не волшебным образом делаетstd::function
(который, как вы сами видели, имеет свои недостатки) стандартом де-факто. - person Christian Rau   schedule 04.02.2013std::function
, когда он вам нужен. Проблема в том, что это ерунда, потому чтоstd::function
- это шаблон класса. - person Lightness Races in Orbit   schedule 06.02.2013std::function
, игнорируя, что это использование template было немного неточно. Конечно,std::function
тоже является шаблоном, но, в конце концов, он более специализирован / менее шаблонен, чем простой аргумент шаблона. - person Christian Rau   schedule 06.02.2013now()
должны быть наhigh_resolution_clock
. Эта ошибка распространилась на все приведенные ниже фрагменты! - person akim   schedule 18.09.2014