Почему тернарные и логические операторы более эффективны, чем if-ветви?

Я наткнулся на этот вопрос/ответ, в котором упоминается, что на большинстве языков логические операторы, такие как:

x == y && doSomething();

может быть быстрее, чем делать то же самое с веткой if:

if(x == y) {
  doSomething();
}

Точно так же он говорит, что тернарный оператор:

x = y == z ? 0 : 1

обычно быстрее, чем использование ветки if:

if(y == z) {
  x = 0;
} else {
  x = 1;
}

Это заставило меня поискать в Google, что привело меня к этот фантастический ответ, объясняющий предсказание ветвлений.

По сути, это говорит о том, что ЦП работает на очень высоких скоростях, и вместо того, чтобы замедляться для вычисления каждой if ветви, он пытается угадать, какой результат будет иметь место, и размещает соответствующие инструкции в своем конвейере. Но если он сделает неверное предположение, ему придется создать резервную копию и пересчитать соответствующие инструкции.

Но это все еще не объясняет мне, почему логические операторы или тернарный оператор обрабатываются иначе, чем if ветвей. Поскольку центральный процессор не знает результата x == y, не должен ли он по-прежнему гадать, поместить ли вызов doSomething() (и, следовательно, весь код doSomething) в свой конвейер? И, следовательно, резервное копирование, если его предположение было неверным? Точно так же для тернарного оператора не должен ли ЦП угадывать, будет ли y == z оцениваться как true при определении того, что хранить в x, и делать резервную копию, если его предположение было неверным?

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


person A. Duff    schedule 03.06.2015    source источник
comment
Возможно, он должен что-то сделать, чтобы первый оператор был выражением, а другой — блоком кода, означающим jmp.   -  person fsacer    schedule 03.06.2015
comment
Это больше вопрос компилятора, чем вопрос процессора. Тернарные операторы функционально почти такие же, как ветки, когда используются таким образом. Но по какой-то причине основные компиляторы, такие как MSVC и GCC, чаще выдают условную ветвь при использовании тернарного оператора.   -  person Mysticial    schedule 03.06.2015
comment
Булевы операторы часто компилируются в виде ветвей. ЦП не может отличить. Но затем некоторые ветки также компилируются в инструкции без веток. Зависит от качества оптимизатора. Если мы говорим здесь о C, компилятор сделает это за вас.   -  person usr    schedule 03.06.2015


Ответы (1)


Краткий ответ - это просто не так. Хотя помощь в прогнозировании ветвлений может улучшить вашу производительность, использование этого как части логического оператора не изменяет скомпилированный код. Если вы хотите помочь в прогнозировании ветвлений, используйте __builtin_expect (для GNU)

Чтобы подчеркнуть, давайте сравним вывод компилятора:

#include <stdio.h>


int main(){
        int foo;

        scanf("%d", &foo); /*Needed to eliminate optimizations*/

#ifdef IF       
        if (foo)
                printf("Foo!");
#else
        foo &&  printf("Foo!");
#endif 
        return 0;
}

Для gcc -O3 branch.c -DIF Получаем:

0000000000400540 <main>:
  400540:       48 83 ec 18             sub    $0x18,%rsp
  400544:       31 c0                   xor    %eax,%eax
  400546:       bf 68 06 40 00          mov    $0x400668,%edi
  40054b:       48 8d 74 24 0c          lea    0xc(%rsp),%rsi
  400550:       e8 e3 fe ff ff          callq  400438 <__isoc99_scanf@plt>
  400555:       8b 44 24 0c             mov    0xc(%rsp),%eax
  400559:       85 c0                   test   %eax,%eax #This is the relevant part
  40055b:       74 0c                   je     400569 <main+0x29>
  40055d:       bf 6b 06 40 00          mov    $0x40066b,%edi
  400562:       31 c0                   xor    %eax,%eax
  400564:       e8 af fe ff ff          callq  400418 <printf@plt>
  400569:       31 c0                   xor    %eax,%eax
  40056b:       48 83 c4 18             add    $0x18,%rsp
  40056f:       c3                      retq 

А для gcc -O3 branch.c

0000000000400540 <main>:
  400540:       48 83 ec 18             sub    $0x18,%rsp
  400544:       31 c0                   xor    %eax,%eax
  400546:       bf 68 06 40 00          mov    $0x400668,%edi
  40054b:       48 8d 74 24 0c          lea    0xc(%rsp),%rsi
  400550:       e8 e3 fe ff ff          callq  400438 <__isoc99_scanf@plt>
  400555:       8b 44 24 0c             mov    0xc(%rsp),%eax
  400559:       85 c0                   test   %eax,%eax
  40055b:       74 0c                   je     400569 <main+0x29>
  40055d:       bf 6b 06 40 00          mov    $0x40066b,%edi
  400562:       31 c0                   xor    %eax,%eax
  400564:       e8 af fe ff ff          callq  400418 <printf@plt>
  400569:       31 c0                   xor    %eax,%eax
  40056b:       48 83 c4 18             add    $0x18,%rsp
  40056f:       c3                      retq 

Это точно такой же код.

Вопрос, на который вы ссылаетесь, измеряет производительность для JAVAScript. Обратите внимание, что там это может быть интерпретировано (поскольку сценарий Java интерпретируется или JIT в зависимости от версии) для чего-то другого для двух случаев. В любом случае JavaScript не лучший вариант для изучения производительности.

person kipodi    schedule 03.06.2015