Boost.Phoenix
От: x-code  
Дата: 29.12.14 10:02
Оценка:
В продолжение темы про буст
Автор: x-code
Дата: 25.12.14

Изучаю и конспектирую понемногу документацию. Наткнулся на Boost.Phoenix.
И не понимаю, что дает эта библиотека в сравнении с обычными лямбда-функциями, boost.lambda и boost.bind.
"Все есть функции" — ну ОК. Функиональные обертки val и ref — понятно, вместо переменной делается функция которая возвращает эту переменную или ссылку на нее.
"arg" — уже менее понятно. Что за синтаксис
int i=3;
std::cout << arg1(i) << std::endl;        // prints 3

и как это увязывается с плейсхолдерами _1 _2 и т.д., при том что утверждается что arg1 и _1 это вроде как одно и то же?
"lazy statements" — вполне понятно, сделали набор функциональных объектов, эмулирующих работу обычных управляющих операторов. С квадратными скобками вместо фигурных красиво получилось
"lazy functions" — вообще непонятно, зачем это и чем обычные функции отличаются от таких?
И в целом вся библиотека для чего?
Re: Boost.Phoenix
От: PM  
Дата: 29.12.14 10:25
Оценка: 10 (2) +1
Здравствуйте, x-code, Вы писали:

XC>И в целом вся библиотека для чего?


Из уже упомянутой в той теме книги The Boost C++ libraries:

Boost.Phoenix is the most important Boost library for functional programming. While libraries like Boost.Bind or Boost.Lambda provide some support for functional programming, Boost.Phoenix includes the features of these libraries and goes beyond them.

In functional programming, functions are objects and can be processed like objects. With Boost.Phoenix, it is possible for a function to return another function as a result. It is also possible to pass a function as a parameter to another function. Because functions are objects, it’s possible to distinguish between instantiation and execution. Accessing a function isn’t equal to executing it.


В эпоху до С++11 вероятно имела некоторый смысл, сейчас при наличии лямбд и std::function мне кажется неактульной. Из той же книги:

Tip

Don’t use Boost.Phoenix to create complex functions. It is better to use lambda functions from C++11. While Boost.Phoenix comes close to C++ syntax, using keywords like if_ or code blocks between square brackets doesn’t necessarily improve readability.


Я когда-то пытался воспользоваться Boost.Phoenix для semantic action в парсере на Boost.Spririt, но код быстро стал нечитаемым, так что пришлось сделать обычный function object.
Re[2]: Boost.Phoenix
От: x-code  
Дата: 29.12.14 11:00
Оценка:
Здравствуйте, PM, Вы писали:

PM>Из уже упомянутой в той теме книги The Boost C++ libraries:

PM>

PM>Boost.Phoenix is the most important Boost library for functional programming. While libraries like Boost.Bind or Boost.Lambda provide some support for functional programming, Boost.Phoenix includes the features of these libraries and goes beyond them.PM>


Вот, в документации к бусту тоже несколько раз упоминается что Phoenix это какой-то "шаг вперед" по сравнению с лямбдами и функицональными объектами. А в чем он, этот шаг? Я не то чтобы использовать ее хочу, а скорее понять мысли разработчиков. Может быть есть таки что-то такое еше более высокого уровня чем лямбды, чего я не понимаю?
(кстати за ссылку спасибо, у меня была оффлайн версия книги — оказывается она устарела, в ней нет phoenix и еще много чего, а я только сейчас это понял)

PM>

In functional programming, functions are objects and can be processed like objects. With Boost.Phoenix, it is possible for a function to return another function as a result. It is also possible to pass a function as a parameter to another function. Because functions are objects, it’s possible to distinguish between instantiation and execution. Accessing a function isn’t equal to executing it.


Это же обычный std::function<> тоже умеет? Или нет?
Re[3]: Boost.Phoenix
От: PM  
Дата: 29.12.14 16:28
Оценка:
Здравствуйте, x-code, Вы писали:

XC>Вот, в документации к бусту тоже несколько раз упоминается что Phoenix это какой-то "шаг вперед" по сравнению с лямбдами и функицональными объектами. А в чем он, этот шаг? Я не то чтобы использовать ее хочу, а скорее понять мысли разработчиков. Может быть есть таки что-то такое еше более высокого уровня чем лямбды, чего я не понимаю?


Могу предположить, что более 10 лет назад, когда создавались boost.phoenix, boost.lambda это был шаг вперед по сравнению с определением функциональных объектов. Я думаю, что с появлением поддержки C++11 в популярных компиляторах, часть библиотек в Boost утратили свою актуальность (smart_ptr, regex, tuple, thread) и то же относится к boost.phoenix, boost.lambda.

PM>>

In functional programming, functions are objects and can be processed like objects. With Boost.Phoenix, it is possible for a function to return another function as a result. It is also possible to pass a function as a parameter to another function. Because functions are objects, it’s possible to distinguish between instantiation and execution. Accessing a function isn’t equal to executing it.


XC>Это же обычный std::function<> тоже умеет? Или нет?


Да, по крайне мере мне хватает встроенных лямбд и std::function для их хранения.

Сегодня вот пришлось воспользоваться std::bind чтобы захватить в лямбде std::unique_ptr, но с выходом С++14 потребность в std::bind отпадет: http://stackoverflow.com/questions/8640393/move-capture-in-lambda
Re[4]: Boost.Phoenix
От: jazzer Россия Skype: enerjazzer
Дата: 29.12.14 17:16
Оценка: 4 (1) +1
Здравствуйте, PM, Вы писали:

PM>Здравствуйте, x-code, Вы писали:


XC>>Вот, в документации к бусту тоже несколько раз упоминается что Phoenix это какой-то "шаг вперед" по сравнению с лямбдами и функицональными объектами. А в чем он, этот шаг? Я не то чтобы использовать ее хочу, а скорее понять мысли разработчиков. Может быть есть таки что-то такое еше более высокого уровня чем лямбды, чего я не понимаю?


PM>Могу предположить, что более 10 лет назад, когда создавались boost.phoenix, boost.lambda это был шаг вперед по сравнению с определением функциональных объектов. Я думаю, что с появлением поддержки C++11 в популярных компиляторах, часть библиотек в Boost утратили свою актуальность (smart_ptr, regex, tuple, thread) и то же относится к boost.phoenix, boost.lambda.


C++14. В C++11 лямбды мономорфные, в отличие от полиморфных бустовских (и lambda, и phoenix).
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Boost.Phoenix
От: nen777w  
Дата: 01.01.15 00:38
Оценка:
XC>"lazy functions" — вообще непонятно, зачем это и чем обычные функции отличаются от таких?
XC>И в целом вся библиотека для чего?

вопрос о полиморфной лямбде
Автор: nen777w
Дата: 21.03.13
Re: Boost.Phoenix
От: swingus  
Дата: 02.01.15 12:42
Оценка:
Здравствуйте, x-code, Вы писали:
XC>И в целом вся библиотека для чего?

Я, честно говоря, почти ей не пользовался. Но если я правильно понимаю, у boost.phoenix остаётся одно важное премущество — он(а) не делает type erasure, то есть результат компиляции будет максимально эффективным. Я, признаться, прямо не встречал об этом упоминания, но это возможно сделать, а в случае использования boost/std function, bind — нет. Она, кстати, написана поверх boost.proto.
Вы ещё спрашивали про boost.proto. Эта библиотека для построения embedded domain specific languages. Проще говоря, она строит синтаксические деревья для выражений типа a + b * c и ему подобных. У разработчика есть сайт http://ericniebler.com и не очень давно он давал ссылку на webarchive на несколько своих постов, где он даёт краткое введение в эту библиотеку. Рекомендую.
Re[2]: Boost.Phoenix
От: jazzer Россия Skype: enerjazzer
Дата: 02.01.15 13:35
Оценка:
Здравствуйте, swingus, Вы писали:

S>Здравствуйте, x-code, Вы писали:

XC>>И в целом вся библиотека для чего?

S>Я, честно говоря, почти ей не пользовался. Но если я правильно понимаю, у boost.phoenix остаётся одно важное премущество — он(а) не делает type erasure, то есть результат компиляции будет максимально эффективным. Я, признаться, прямо не встречал об этом упоминания, но это возможно сделать, а в случае использования boost/std function, bind — нет. Она, кстати, написана поверх boost.proto.


bind тоже не делает type erasure, равно как и boost::lambda.
Собственно, единственное, что его делает — это boost/std::function, и именно для этого она и создана.
Все остальное генерит свой тип, почти как полиморфные лямбды С++14.

S>Вы ещё спрашивали про boost.proto. Эта библиотека для построения embedded domain specific languages. Проще говоря, она строит синтаксические деревья для выражений типа a + b * c и ему подобных. У разработчика есть сайт http://ericniebler.com и не очень давно он давал ссылку на webarchive на несколько своих постов, где он даёт краткое введение в эту библиотеку. Рекомендую.


+1
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Boost.Phoenix
От: swingus  
Дата: 02.01.15 14:01
Оценка:
Здравствуйте, jazzer, Вы писали:



J>bind тоже не делает type erasure, равно как и boost::lambda.


Буду знать теперь.
Re[3]: Boost.Phoenix
От: swingus  
Дата: 04.02.15 06:23
Оценка:
Здравствуйте, jazzer, Вы писали:


J>bind тоже не делает type erasure, равно как и boost::lambda.


Ага, я всё-таки прав был, если верить Скотту

...Во-вторых, лямбда-выражения как правило работают быстрее. Дело в том что std::bind захватывает и хранит указатель на функцию, поэтому компилятор имеет мало шансов встроить (inline) ее, ...

Re[4]: встраивается ли boost/std::bind
От: jazzer Россия Skype: enerjazzer
Дата: 04.02.15 09:40
Оценка:
Здравствуйте, swingus, Вы писали:

S>Здравствуйте, jazzer, Вы писали:


J>>bind тоже не делает type erasure, равно как и boost::lambda.


S>Ага, я всё-таки прав был, если верить Скотту

S>

S>...Во-вторых, лямбда-выражения как правило работают быстрее. Дело в том что std::bind захватывает и хранит указатель на функцию, поэтому компилятор имеет мало шансов встроить (inline) ее, ...


какое отношение это имеет к type erasure? его по-прежнему нету, в отличие от boost/std::function.

Плюс bind берет в качестве аргумента не только указатель на функцию, но и функциональный объект — а его operator() замечательно встраивается.

Плюс при стандартном использовании, когда результат bind не сохраняется куда-то надолго (что, скорее всего, приведет к type erasure, потому что будет храниться в виде function), а передается в шаблонную функцию, которая тут же и раскрывается (типа std::sort и т.п.) — я не вижу проблем со встраиванием такого вызова — оно после всех inline-преобразований должно превратиться просто в вызов функции, известной на этапе компиляции.
По крайней мере, я бы удивился, если бы компилятор не смог встроить код функции, взятой через bind.

(через полчаса)
З.Ы. Практика — критерий истины. Будем встраивать функцию f, сначала по-простому (функции gN), а потом на массиве (функции aN):
using namespace std;
using namespace placeholders;

int f(int x, int y) { return x+y; }

int __attribute__((noinline)) g1(int x, int y)
{
  return f(x,y);
}

int __attribute__((noinline)) g2(int x, int y)
{
  auto w = bind(f, _1, _2);
  return w(x, y);
}

int __attribute__((noinline)) g3(int x, int y)
{
  auto w = bind(&f, _1, y);
  return w(x);
}

int __attribute__((noinline)) g4(int x, int y)
{
  auto w = bind(&f, x, _1);
  return w(y);
}

// теперь массив
int arr[] = {1,2,3,4,5,6,7,8,9};

int __attribute__((noinline)) a1(int y)
{
  transform( begin(arr), end(arr), begin(arr), [y](int& a){ return f(a, y);} );
  return arr[0];
}

int __attribute__((noinline)) a2(int y)
{
  transform( begin(arr), end(arr), begin(arr), bind(f, _1, y) );
  return arr[0];
}

компилирую g++4.9.1 --std=c++11 -O2
результат:
0000000000400600 <f(int, int)>:
  400600:       8d 04 37                lea    (%rdi,%rsi,1),%eax
  400603:       c3                      retq
  400604:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)
  40060b:       00 00 00 00 00

0000000000400610 <g1(int, int)>:
  400610:       8d 04 37                lea    (%rdi,%rsi,1),%eax
  400613:       c3                      retq
  400614:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)
  40061b:       00 00 00 00 00

0000000000400620 <g2(int, int)>:
  400620:       8d 04 3e                lea    (%rsi,%rdi,1),%eax
  400623:       c3                      retq
  400624:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)
  40062b:       00 00 00 00 00

0000000000400630 <g3(int, int)>:
  400630:       8d 04 3e                lea    (%rsi,%rdi,1),%eax
  400633:       c3                      retq
  400634:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)
  40063b:       00 00 00 00 00

0000000000400640 <g4(int, int)>:
  400640:       8d 04 37                lea    (%rdi,%rsi,1),%eax
  400643:       c3                      retq
  400644:       66 66 66 2e 0f 1f 84    data32 data32 nopw %cs:0x0(%rax,%rax,1)
  40064b:       00 00 00 00 00

0000000000400650 <a1(int)>:
  400650:       b8 a0 1b 40 00          mov    $0x401ba0,%eax
  400655:       0f 1f 00                nopl   (%rax)
  400658:       01 38                   add    %edi,(%rax)
  40065a:       48 83 c0 04             add    $0x4,%rax
  40065e:       48 3d c4 1b 40 00       cmp    $0x401bc4,%rax
  400664:       75 f2                   jne    400658 <a1(int)+0x8>
  400666:       8b 05 34 15 00 00       mov    0x1534(%rip),%eax        # 401ba0 <arr>
  40066c:       c3                      retq
  40066d:       0f 1f 00                nopl   (%rax)

0000000000400670 <a2(int)>:
  400670:       b8 a0 1b 40 00          mov    $0x401ba0,%eax
  400675:       0f 1f 00                nopl   (%rax)
  400678:       01 38                   add    %edi,(%rax)
  40067a:       48 83 c0 04             add    $0x4,%rax
  40067e:       48 3d c4 1b 40 00       cmp    $0x401bc4,%rax
  400684:       75 f2                   jne    400678 <a2(int)+0x8>
  400686:       8b 05 14 15 00 00       mov    0x1514(%rip),%eax        # 401ba0 <arr>
  40068c:       c3                      retq
  40068d:       00 00                   add    %al,(%rax)

найдите 10 отличий.

Так что либо Скотт неправ, либо ты читаешь что-то сильно устаревшее.

З.З.Ы. C boost::bind точно такой же результат, если что — это на случай подозрений, что компилятор знает что-то особенное про свой std::bind.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[5]: встраивается ли boost/std::bind
От: uzhas Ниоткуда  
Дата: 04.02.15 12:58
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Плюс при стандартном использовании, когда результат bind не сохраняется куда-то надолго

я бы это назвал "нестандартным" использованием bind (результат bind имеет unspecified type, так что напрямую работать хлопотно), лично я обычно его сразу кладу в function. еще в студии были баги, если попытаться сразу применить результат bind к аргументу

J>Так что либо Скотт неправ, либо ты читаешь что-то сильно устаревшее.

я думаю, что цитата вырвана из контекста и имеется в виду type erasure

вот тут немного исследовали встраиваемость std::function, результаты были печальные, только clang хорошо себя повел
http://rsdn.ru/forum/cpp/5658184.flat
Автор: uzhas
Дата: 23.06.14
Re[5]: встраивается ли boost/std::bind
От: swingus  
Дата: 04.02.15 14:36
Оценка:
Ну хранение указателя на функцию — это и есть реализация type erasure для функций.

Здравствуйте, jazzer, Вы писали:

> Так что либо Скотт неправ, либо ты читаешь что-то сильно устаревшее.


Это из аннотации к новой книге.

А ассемблерный код в данном случае ничего не доказывает. Точно также компилятор может заменить виртуальные вызовы на прямые в простых случаях.
Re[5]: встраивается ли boost/std::bind
От: Evgeny.Panasyuk Россия  
Дата: 04.02.15 19:04
Оценка:
Здравствуйте, jazzer, Вы писали:
то
S>>Ага, я всё-таки прав был, если верить Скотту
S>>

S>>...Во-вторых, лямбда-выражения как правило работают быстрее. Дело в том что std::bind захватывает и хранит указатель на функцию, поэтому компилятор имеет мало шансов встроить (inline) ее, ...

J>[...]
J>Так что либо Скотт неправ, либо ты читаешь что-то сильно устаревшее.

Уверен что современные компиляторы легко оптимизируют подобные косвенные вызовы.
Думаю имелось в виду что шансов встроить указатель на функцию меньше (а не "мало"), так как очевидно код привязан не к типу, а к значению указателя.
Re[6]: встраивается ли boost/std::bind
От: swingus  
Дата: 04.02.15 21:33
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Уверен что современные компиляторы легко оптимизируют подобные косвенные вызовы.

У современного компилятора столько же шансов соптимизировать std::bind(), сколько виртуальную функцию.

EP>Думаю имелось в виду что шансов встроить указатель на функцию меньше (а не "мало"), так как очевидно код привязан не к типу, а к значению указателя.

Вот как раз Скотт говорит, что хранится указатель на функцию, так что это "очевидно" совсем не очевидно.
Re[7]: встраивается ли boost/std::bind
От: Evgeny.Panasyuk Россия  
Дата: 04.02.15 22:57
Оценка: +1
Здравствуйте, swingus, Вы писали:

EP>>Уверен что современные компиляторы легко оптимизируют подобные косвенные вызовы.

S>У современного компилятора столько же шансов соптимизировать std::bind(), сколько виртуальную функцию.

Какой конкретно bind? С указателем на функцию внутри?
Это смотря как считать. Результат bind часто используется в контекстах, где передаётся в шаблон функции, который где-то внутри сразу его вызывает (смотри пример jazzer'а), то есть грубо говоря статический полиморфизм. И после инлайнинга, легко доказывается что этот указатель на функцию константен.
А виртуальные функции практически всегда используются для динамического полиморфизма, при котором в месте вызова статически не известно какой конкретно метод нужно вызывать (хотя есть эвристические методы типа PGO).
И уж шансов соптимизировать подобный bind на порядки выше чем виртуальный вызов.

А вот если сравнивать с тем bind'ом, который принимает функциональные объекты — то тут вообще не о чём спорить, так как какой конкретно код нужно вызывать закодировано в типе возвращаемого объекта.

EP>>Думаю имелось в виду что шансов встроить указатель на функцию меньше (а не "мало"), так как очевидно код привязан не к типу, а к значению указателя.

S>Вот как раз Скотт говорит, что хранится указатель на функцию, так что это "очевидно" совсем не очевидно.

Так я и говорю, что здесь код привязан к значению, то есть хранится значение. Более того, для разных входных указателей на функции, но с одинаковым типом, получится одинаковый тип результата bind
Re[6]: встраивается ли boost/std::bind
От: jazzer Россия Skype: enerjazzer
Дата: 05.02.15 00:15
Оценка: +2
Здравствуйте, uzhas, Вы писали:

U>Здравствуйте, jazzer, Вы писали:


J>>Плюс при стандартном использовании, когда результат bind не сохраняется куда-то надолго

U>я бы это назвал "нестандартным" использованием bind (результат bind имеет unspecified type, так что напрямую работать хлопотно), лично я обычно его сразу кладу в function. еще в студии были баги, если попытаться сразу применить результат bind к аргументу

Наоборот, это самое что ни на есть стандартное использование — передача во всякие std::transform/for_each/accumulate...
Даже в С++98 стандартные байндеры использовались именно для таких случаев, никакого обобщенного хранилища типа function не было тогда.

J>>Так что либо Скотт неправ, либо ты читаешь что-то сильно устаревшее.

U>я думаю, что цитата вырвана из контекста и имеется в виду type erasure

U>вот тут немного исследовали встраиваемость std::function, результаты были печальные, только clang хорошо себя повел

U>http://rsdn.ru/forum/cpp/5658184.flat
Автор: uzhas
Дата: 23.06.14


я не обсуждаю function — в ней type erasure со всеми вытекающими. Речь была именно о bind.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[6]: встраивается ли boost/std::bind
От: jazzer Россия Skype: enerjazzer
Дата: 05.02.15 00:18
Оценка:
Здравствуйте, swingus, Вы писали:

S>Ну хранение указателя на функцию — это и есть реализация type erasure для функций.


это как? указатель на функцию инициализируется в таком коде точно так же, как это было бы в лямбде — и там, и там все известно на этапе компиляции.
А если такой указатель к тебе пришел неизвестно откуда, то как бы ты его не использовал — в байнде или в лямбде — ничего встраиваться не будет, очевидно.

>> Так что либо Скотт неправ, либо ты читаешь что-то сильно устаревшее.

S>Это из аннотации к новой книге.

Ну печально, чо

S>А ассемблерный код в данном случае ничего не доказывает. Точно также компилятор может заменить виртуальные вызовы на прямые в простых случаях.


Так это не какие-то особенно простые случаи, это самый обычный сценарий использования bind — передача в алгоритм.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[6]: встраивается ли boost/std::bind
От: jazzer Россия Skype: enerjazzer
Дата: 05.02.15 00:20
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Уверен что современные компиляторы легко оптимизируют подобные косвенные вызовы.

именно.

EP>Думаю имелось в виду что шансов встроить указатель на функцию меньше (а не "мало"), так как очевидно код привязан не к типу, а к значению указателя.


Если значение указателя известно во время компиляции, то я не вижу вообще никакой разницы между лямбдой и байндером — компилятор встроит и там, и там.
И если неизвестен — тоже: он останется неизвестным и внутри лямбды и встроен не будет ни там, ни там.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: встраивается ли boost/std::bind
От: jazzer Россия Skype: enerjazzer
Дата: 05.02.15 00:24
Оценка:
Здравствуйте, swingus, Вы писали:

S>Здравствуйте, Evgeny.Panasyuk, Вы писали:


EP>>Уверен что современные компиляторы легко оптимизируют подобные косвенные вызовы.

S>У современного компилятора столько же шансов соптимизировать std::bind(), сколько виртуальную функцию.
И столько же, сколько лямбду.
А именно — если компилятору что-то известно во время компиляции, это будет использовано независимо от того, в байнде это, в лямбде или в виртуальном вызове по известному во время компиляции типу. А если неизвестно, то и лямбда никак тут не поможет.

EP>>Думаю имелось в виду что шансов встроить указатель на функцию меньше (а не "мало"), так как очевидно код привязан не к типу, а к значению указателя.

S>Вот как раз Скотт говорит, что хранится указатель на функцию, так что это "очевидно" совсем не очевидно.

Ассемблер тебя не убедил? Ты там видишь хранящийся указатель на функцию где-нибудь?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: встраивается ли boost/std::bind
От: Evgeny.Panasyuk Россия  
Дата: 05.02.15 01:04
Оценка:
Здравствуйте, jazzer, Вы писали:

EP>>Думаю имелось в виду что шансов встроить указатель на функцию меньше (а не "мало"), так как очевидно код привязан не к типу, а к значению указателя.

J>Если значение указателя известно во время компиляции, то я не вижу вообще никакой разницы между лямбдой и байндером — компилятор встроит и там, и там.

Во-первых разница в том что именно компилятору приходится встраивать.
В случае с функциональным объектом — ему нужно только встроить обычный вызов, грубо говоря даже не прикладывая никаких усилий для доказательства того, что вызваться будет одна и та же функция. Есть тип, и с этим типом связанна конкретная функция, другой быть не может.
В случае с указателем на функцию — компилятору после инлайнинга требуется сделать constant propagation, чтобы убедится что вызов происходит по константному указателю, и только после этого встроить этот косвенный вызов.

Во-вторых разница может проявится при разных опциях компилятора: -Os, max-inline-recursive-depth, не говоря уже об -Od.
Например на -Os компилятор имеет полное моральное право сгенерировать только один вариант std::transform для двух вызовов с bind'ами, отличающимися только указателями на функции, но не типами.
Отредактировано 05.02.2015 1:08 Evgeny.Panasyuk . Предыдущая версия .
Re[8]: встраивается ли boost/std::bind
От: jazzer Россия Skype: enerjazzer
Дата: 05.02.15 02:41
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>В случае с указателем на функцию — компилятору после инлайнинга требуется сделать constant propagation, чтобы убедится что вызов происходит по константному указателю, и только после этого встроить этот косвенный вызов.


Ну я поэтому специально использовал самый обычный рабоче-крестьянский -О2, а не какую-то высокоуровневую эзотерику об осьмнадцати ключах
Сам видишь, на -О2 все замечательно встраивается

EP>Во-вторых разница может проявится при разных опциях компилятора: -Os, max-inline-recursive-depth, не говоря уже об -Od.

EP>Например на -Os компилятор имеет полное моральное право сгенерировать только один вариант std::transform для двух вызовов с bind'ами, отличающимися только указателями на функции, но не типами.

Я думаю, он то же самое способен сделать и с лямбдами, если это уменьшит код — ведь в лямбде тоже просто вызов функции, и компилятор может так и оставить там callq, не встраивая ничего.
А может и встроить в обоих случаях, если встраивание уменьшает код.
Тут вопрос только в том, насколько компилятор готов заморочиться — потому что, по-хорошему, для реальной минимизации кода надо компилировать несколько вариантов, а потом выбирать самый короткий. А можно просто тупо отрубить определенные классы оптимизаций, которые "обычно" раздувают код, даже если в конкретном случае они могли бы код и сдуть. Менее эффективно в смысле результата, зато быстро в сымсле скорости компиляции.

(Сейчас набегут и скажут, что С++ — язык для гиков)
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[9]: встраивается ли boost/std::bind
От: Evgeny.Panasyuk Россия  
Дата: 05.02.15 06:37
Оценка:
Здравствуйте, jazzer, Вы писали:

EP>>В случае с указателем на функцию — компилятору после инлайнинга требуется сделать constant propagation, чтобы убедится что вызов происходит по константному указателю, и только после этого встроить этот косвенный вызов.

J>Ну я поэтому специально использовал самый обычный рабоче-крестьянский -О2, а не какую-то высокоуровневую эзотерику об осьмнадцати ключах
J>Сам видишь, на -О2 все замечательно встраивается

Да, но всё же шансов на встраивание тут меньше, так как требует больше работы со стороны оптимизатора. То есть, например, если взять много подобных тестов, взять разных версий компиляторов лет за 10-15 лет, то думаю что функциональные объекты будут встраиваться намного охотней.
Вообще говоря, когда стоит подобный выбор, я стараюсь использовать именно функциональные объекты, как раз по этой причине. В каких-то же совсем некритичных местах, если bind на функцию будет выглядеть лаконичней, то так уж и быть, можно и его.

EP>>Во-вторых разница может проявится при разных опциях компилятора: -Os, max-inline-recursive-depth, не говоря уже об -Od.

EP>>Например на -Os компилятор имеет полное моральное право сгенерировать только один вариант std::transform для двух вызовов с bind'ами, отличающимися только указателями на функции, но не типами.
J>Я думаю, он то же самое способен сделать и с лямбдами, если это уменьшит код — ведь в лямбде тоже просто вызов функции, и компилятор может так и оставить там callq, не встраивая ничего.

Я о другом — когда мы например передаём в std::sort разные указатели на функции, но одинаковых типов (типы итераторов тоже одинаковые), мы получаем одну instantiation std::sort.
Если же мы передаём две лямбды — то у них будут разные типы, и соответственно у std::sort будет две instantiations. Конечно продвинутый компилятор, при -Os может догадаться их как-нибудь объединить, но это очевидно сложнее чем просто оставить одну instantiation в случае указателей на функции.

J>А может и встроить в обоих случаях, если встраивание уменьшает код.


Да, бывает и такое (причём часто ). А ещё иногда бывает что -Os делает код быстрее.

J>Тут вопрос только в том, насколько компилятор готов заморочиться — потому что, по-хорошему, для реальной минимизации кода надо компилировать несколько вариантов, а потом выбирать самый короткий. А можно просто тупо отрубить определенные классы оптимизаций, которые "обычно" раздувают код, даже если в конкретном случае они могли бы код и сдуть. Менее эффективно в смысле результата, зато быстро в сымсле скорости компиляции.


Да уж, тут прям целая оптимизационная задача.
Кстати, при оптимизации на скорость иногда имеет смысл компилировать код налету, под конкретные данные. А если возможных параметров много, и как они поведут себя на пришедших данных неизвестно — то можно натравить на них какой-нибудь оптимизационный метод, то есть делать серию сборок, и выбирать быстрейшую. Имеет смысл тогда, когда итераций много, но основная часть обрабатываемых данных остаётся неизменной (например в некоторых задачах линейной алгебры нужно умножать большую константную разряженную матрицу на разные вектора, которые вычисляются в процессе итераций).

J>(Сейчас набегут и скажут, что С++ — язык для гиков)


В нашу песочницу из других редко кто заходит
Re[5]: встраивается ли boost/std::bind
От: Evgeny.Panasyuk Россия  
Дата: 06.10.15 17:55
Оценка: +1
Здравствуйте, jazzer, Вы писали:

J>З.Ы. Практика — критерий истины. Будем встраивать функцию f, сначала по-простому (функции gN), а потом на массиве (функции aN):

J>...
J>
J>int __attribute__((noinline)) a1(int y)
J>{
J>  transform( begin(arr), end(arr), begin(arr), [y](int& a){ return f(a, y);} );
J>  return arr[0];
J>}

J>int __attribute__((noinline)) a2(int y)
J>{
J>  transform( begin(arr), end(arr), begin(arr), bind(f, _1, y) );
J>  return arr[0];
J>}
J>

J>компилирую g++4.9.1 --std=c++11 -O2
J>результат:
J>...
J>[/asm]
J>найдите 10 отличий.

Вариация: допустим компилятор не заинлайнил transform по какой-то причине
template<typename I, typename O, typename F>
__attribute__((noinline)) O transform(I first, I last, O out, F f)
{
    for(; first!=last; ++first, ++out)
        *out = f(*first);
}

int f(int x, int y) { return x*y; }

int test_lambda(int (&arr)[1111], int y)
{
    transform( begin(arr), end(arr), begin(arr), [=](int &a){ return f(a, y);} );
    return arr[0];
}

int test_bind(int (&arr)[1111], int y)
{
    transform( begin(arr), end(arr), begin(arr), bind(f, _1, y) );
    return arr[0];
}

Результирующий ASM:
; g++ -std=c++14 -O2 main.cpp -S && cat main.s
...
_Z9transformIPiS0_Z11test_lambdaRA1111_iiEUlRiE_ET0_T_S6_S5_T1_:
.LFB1390:
    .cfi_startproc
    cmpq    %rsi, %rdi
    je    .L4
    .p2align 4,,10
    .p2align 3
.L6:
    movl    (%rdi), %eax
    addq    $4, %rdi
    addq    $4, %rdx
    imull    %ecx, %eax
    movl    %eax, -4(%rdx)
    cmpq    %rdi, %rsi
    jne    .L6
.L4:
    xorl    %eax, %eax
    ret
    .cfi_endproc
...
_Z11test_lambdaRA1111_ii:
.LFB1279:
    .cfi_startproc
    movl    %esi, %ecx
    leaq    4444(%rdi), %rsi
    movq    %rdi, %r8
    movq    %rdi, %rdx
    call    _Z9transformIPiS0_Z11test_lambdaRA1111_iiEUlRiE_ET0_T_S6_S5_T1_
    movl    (%r8), %eax
    ret
...
_Z9transformIPiS0_St5_BindIFPFiiiESt12_PlaceholderILi1EEiEEET0_T_S9_S8_T1_:
.LFB1429:
    .cfi_startproc
    cmpq    %rsi, %rdi
    je    .L17
    pushq    %r13
    .cfi_def_cfa_offset 16
    .cfi_offset 13, -16
    pushq    %r12
    .cfi_def_cfa_offset 24
    .cfi_offset 12, -24
    movq    %rsi, %r13
    pushq    %rbp
    .cfi_def_cfa_offset 32
    .cfi_offset 6, -32
    pushq    %rbx
    .cfi_def_cfa_offset 40
    .cfi_offset 3, -40
    movq    %rdx, %rbp
    movq    %rdi, %rbx
    movq    %rcx, %r12
    subq    $8, %rsp
    .cfi_def_cfa_offset 48
    .p2align 4,,10
    .p2align 3
.L14:
    movl    (%rbx), %edi
    addq    $4, %rbx
    movl    8(%r12), %esi
    addq    $4, %rbp
    call    *(%r12)
    movl    %eax, -4(%rbp)
    cmpq    %rbx, %r13
    jne    .L14
    addq    $8, %rsp
    .cfi_def_cfa_offset 40
    xorl    %eax, %eax
    popq    %rbx
    .cfi_restore 3
    .cfi_def_cfa_offset 32
    popq    %rbp
    .cfi_restore 6
    .cfi_def_cfa_offset 24
    popq    %r12
    .cfi_restore 12
    .cfi_def_cfa_offset 16
    popq    %r13
    .cfi_restore 13
    .cfi_def_cfa_offset 8
    ret
.L17:
    xorl    %eax, %eax
    ret
    .cfi_endproc
...
_Z9test_bindRA1111_ii:
.LFB1283:
    .cfi_startproc
    pushq    %rbx
    .cfi_def_cfa_offset 16
    .cfi_offset 3, -16
    movq    %rdi, %rdx
    movq    %rdi, %rbx
    subq    $16, %rsp
    .cfi_def_cfa_offset 32
    movl    %esi, 8(%rsp)
    leaq    4444(%rdi), %rsi
    movq    %rsp, %rcx
    movq    $_Z1fii, (%rsp)
    call    _Z9transformIPiS0_St5_BindIFPFiiiESt12_PlaceholderILi1EEiEEET0_T_S9_S8_T1_
    movl    (%rbx), %eax
    addq    $16, %rsp
    .cfi_def_cfa_offset 16
    popq    %rbx
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc

В случае с bind есть indirect call, хотя transform не был заинлайнен в обоих случаях.
Очевидно что локально, внутри самого transform, больше доступной информации в случае с лямбдой. И вот эта локальная разница, может привести к разному результату, хотя глобально во время компиляции доступна вся информация в обоих случаях.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.