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...
Пока на собственное сообщение не было ответов, его можно удалить.