Оператор if vs оператор if-else, что быстрее?

На днях я поспорил с другом по поводу этих двух отрывков. Что быстрее и почему?

value = 5;
if (condition) {
    value = 6;
}

и:

if (condition) {
    value = 6;
} else {
    value = 5;
}

Что, если value - матрица?

Примечание: я знаю, что value = condition ? 6 : 5; существует, и ожидаю, что это будет быстрее, но это был не вариант.

Изменить (запрошено персоналом, поскольку в настоящий момент вопрос отложен):

  • ответьте, рассмотрев либо сборку x86, сгенерированную основными компиляторами (скажем, g ++, clang ++, vc, mingw) как в оптимизированной, так и в неоптимизированной версиях, либо сборку MIPS .
  • если сборки различаются, объясните, почему версия работает быстрее и когда (например, «лучше, потому что ветвление и ветвление не имеют следующей проблемы, бла-бла»)

person Julien__    schedule 04.04.2017    source источник
comment
Оптимизация все это убьет ... неважно ...   -  person The Quantum Physicist    schedule 04.04.2017
comment
Вы можете профилировать его, лично я сомневаюсь, что вы заметите какую-либо разницу, используя современный компилятор.   -  person George    schedule 04.04.2017
comment
Это c ++, а не java, компилятор будет управлять всем, оба одинаковые   -  person nikesh joshi    schedule 04.04.2017
comment
Как бы вы ни смотрели на это, было бы печально, если бы выполнение строго большего количества действий привело бы к более быстрому выполнению ..   -  person Drax    schedule 04.04.2017
comment
@nikeshjoshi, вы, вероятно, правы, учитывая OP с тегом C ++ 11, но не все компиляторы будут оптимизировать.   -  person George    schedule 04.04.2017
comment
Использование value = condition ? 6 : 5; вместо if/else, скорее всего, приведет к генерации того же кода. Посмотрите на вывод сборки, если хотите узнать больше.   -  person Jabberwocky    schedule 04.04.2017
comment
Самое главное в этом случае - избегать ветки, а это здесь самое дорогое. (перезагрузка канала, отмена предварительно загруженных инструкций и т. д.)   -  person Tommylee2k    schedule 04.04.2017
comment
@MichaelWalz: Это зависит от типа value. При использовании _2 _ / _ 3_ сначала должен быть вызван конструктор по умолчанию, а затем оператор присваивания. С ?: вы можете строить прямо на месте.   -  person Matthieu M.    schedule 04.04.2017
comment
Единственный раз, когда имеет смысл микро-оптимизировать скорость, как это, - это внутри цикла, который будет выполняться много-много раз, и либо оптимизатор может оптимизировать все инструкции ветвления, как gcc для этого тривиального примера, либо в реальном мире производительность будет сильно зависеть от правильного предсказания ветвления (обязательная ссылка на stackoverflow.com/questions/11227809/). Если вы неизбежно разветвляетесь внутри цикла, вы можете помочь предиктору ветвления, сгенерировав профиль и перекомпилировав его вместе с ним.   -  person Davislor    schedule 04.04.2017
comment
Ответ: это зависит от обстоятельств. Невозможно сказать изолированно. Если вы хотите знать, что быстрее в ваших конкретных обстоятельствах, вам нужно рассчитать время.   -  person Jim Mischel    schedule 04.04.2017
comment
Разве это не зависит полностью от того, что такое value и что с ним делает оператор присваивания? Если у него есть побочные эффекты, два фрагмента даже не делают одно и то же.   -  person ilkkachu    schedule 05.04.2017
comment
Это слишком общий вопрос. В нем отсутствует sscce и не указывается конкретное оборудование и компилятор. Это означает, что при ответах необходимо делать общие предположения, делая любой ответ правильным.   -  person 2501    schedule 05.04.2017
comment
Лично я бы предположил, что 1) Если condition равно true, вторая версия работает быстрее (первая - это присвоить-сравнить-присвоить-прыжок, вторая - сравнить-присвоить-прыжок). 2) Если condition равно false, оба равны (одно присвоение, одно сравнение и один переход). 3) Если оптимизация включена, это не имеет значения, потому что они обе будут скомпилированы в одно и то же, что компилятор сочтет наиболее эффективным. Предположив, я могу быть удивлен или не удивлен, в зависимости от того, использует ли компилятор тот подход, который, как я ожидаю, будет в неоптимизированном коде.   -  person Justin Time - Reinstate Monica    schedule 06.04.2017
comment
Ответьте на более быстрый вопрос, запустив код. Вы нашли время, чтобы это написать; теперь найдите время, чтобы запустить его. Тот, который работает быстрее, когда вы его запускаете , тем быстрее.   -  person Eric Lippert    schedule 06.04.2017


Ответы (6)


TL; DR: В неоптимизированном коде if без else кажется несущественно более эффективным, но даже при включении самого базового уровня оптимизации код в основном переписывается на value = condition + 5.


Я попробовал и сгенерировал сборку для следующего кода:

int ifonly(bool condition, int value)
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return value;
}

int ifelse(bool condition, int value)
{
    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return value;
}

В gcc 6.3 с отключенной оптимизацией (-O0) соответствующая разница:

 mov     DWORD PTR [rbp-8], 5
 cmp     BYTE PTR [rbp-4], 0
 je      .L2
 mov     DWORD PTR [rbp-8], 6
.L2:
 mov     eax, DWORD PTR [rbp-8]

для ifonly, а для ifelse

 cmp     BYTE PTR [rbp-4], 0
 je      .L5
 mov     DWORD PTR [rbp-8], 6
 jmp     .L6
.L5:
 mov     DWORD PTR [rbp-8], 5
.L6:
 mov     eax, DWORD PTR [rbp-8]

Последний выглядит немного менее эффективным, потому что у него есть дополнительный прыжок, но у обоих есть как минимум два, максимум три назначения, поэтому, если вам действительно не нужно выжать все до последней капли производительности (подсказка: если вы не работаете на космическом шаттле, вы этого не сделаете). , и даже в этом случае вероятно нет) разница не будет заметна.

Однако даже при самом низком уровне оптимизации (-O1) обе функции сводятся к одному и тому же:

test    dil, dil
setne   al
movzx   eax, al
add     eax, 5

что в основном эквивалентно

return 5 + condition;

предполагая, что condition равно нулю или единице. Более высокие уровни оптимизации на самом деле не изменяют результат, за исключением того, что им удается избежать movzx, эффективно обнуляя регистр EAX в начале.


Заявление об ограничении ответственности: вам, вероятно, не следует писать 5 + condition самостоятельно (хотя стандартные гарантии того, что преобразование true в целочисленный тип дает 1), потому что ваше намерение может быть не сразу очевидно для людей, читающих ваш код (которые могут включать ваше будущее я). Цель этого кода - показать, что то, что компилятор производит в обоих случаях (практически) идентично. Сиприан Томояга довольно хорошо заявляет об этом в комментариях:

Задача человека - написать код для людей и позволить компилятору написать код для машины.

person CompuChip    schedule 04.04.2017
comment
Это отличный ответ, и его следует принять. - person dtell; 04.04.2017
comment
Я бы никогда не стал использовать добавление (‹- что питон делает с вами.) - person Ciprian Tomoiagă; 04.04.2017
comment
@CiprianTomoiaga, и если вы не пишете оптимизатор, вам не следует этого делать! Практически во всех случаях вы должны позволить компилятору выполнять подобные оптимизации, особенно если они сильно ухудшают читаемость кода. Только если тестирование производительности выявляет проблемы с определенным фрагментом кода, вам следует даже начать попытки его оптимизировать, и даже тогда держать его чистым и хорошо прокомментированным, и выполнять только оптимизацию, которая дает измеримую разницу. - person Muzer; 04.04.2017
comment
Этот ответ не учитывает вопрос: что, если value является матрицей? Оптимизаторы очень хороши с примитивными типами, но такие же оптимизации не обязательно доступны с пользовательскими типами. - person Matthieu M.; 04.04.2017
comment
@MatthieuM. Определите матрицу. На эту часть вопроса невозможно ответить. В любом случае этот ответ адекватно описывает методы, необходимые для определения результатов для любого типа значения, исчерпывающий список всех возможных типов значений (и для всех компиляторов на всех платформах, если вы хотите быть подробными) это ни возможно, ни полезно. Просто проделайте тот же анализ, что и здесь, но вместо этого с матрицей, или виджетом, или double, или чем-то еще (и с вашим компилятором на вашей целевой платформе). - person Jason C; 04.04.2017
comment
Молодец @CompuChip. Фактически, этот прием сложения очень хорошо изучен (наконец, с теоретической точки зрения). Но что, если мы изменим строки кода value = 6 на value = -1? Будет ли компилятор на том же уровне оптимизации делать return 5 + (condition * -6)? - person nrocha; 04.04.2017
comment
@nrocha: Я мог бы это сделать или использовать cmov, ... - person Matthieu M.; 04.04.2017
comment
@JasonC: Я согласен с тем, что эти техники полезны; Меня беспокоит то, что кто-то может подумать, что эта оптимизация является автоматической, хотя на самом деле она довольно специализирована и полагается на то, что компилятор может оптимизировать количество ненужных выделений / записей в общем случае. - person Matthieu M.; 04.04.2017
comment
@MatthieuM. Упоминание о том, что он довольно специализирован и полагается на то, что компилятор может оптимизировать количество ненужных выделений / записей в общем случае в этом ответе, тогда будет отличной всеобъемлющей стратегией, а чем добавление еще одного примера (что оставляет вас с той же проблемой, только с еще одним конкретным примером). И, конечно же, в более широком плане я в целом убежден, что люди, которым действительно нужно узнать больше, либо , узнают больше, либо в конечном итоге произойдет естественный отбор (и если ни то, ни другое не произойдет, все равно это не имеет значения). :) - person Jason C; 04.04.2017
comment
@CiprianTomoiaga еще одна причина, по которой вам не следует этого делать, заключается в том, что я думаю, что стандарт не утверждает, что приведение true к int всегда дает 1, просто ненулевое значение. Ассемблерный код на самом деле этого не предполагает, это зависит от инструкции setne, которая задокументирована для установки регистра в 0 или 1. Как я пытался подчеркнуть в ответе, позвольте компилятору сделать это, не переходите на микро- оптимизировать самостоятельно! - person CompuChip; 04.04.2017
comment
Я хотел ответить Музеру, но это ничего бы не добавило к теме. Однако я просто хочу повторить, что работа человека - написать код для людей и позволить компилятору написать код для машина. Я сказал это от разработчика компилятора PoV (чего я не знаю, но я немного узнал о них) - person Ciprian Tomoiagă; 04.04.2017
comment
@CiprianTomoiaga: Python действительно позволяет делать condition + 5, если condition является логическим. Многие пользователи Python считают, что этого нельзя допускать, но так оно и есть. - person user2357112 supports Monica; 04.04.2017
comment
@CompuChip: Компьютеры часто знают то, чего не знают люди, а люди часто знают то, чего не знают компьютеры. Учитывая что-то вроде if (x == 23 && x) {...} else switch(x) {...}, перенос кода из if в случай 23 может улучшить производительность, если только x не окажется на 23 больше, чем в какой-то части времени. Компилятор может лучше, чем программист, понимать, какой должна быть доля безубыточности, но программист может знать больше, чем компилятор, о том, как часто x будет 23. - person supercat; 04.04.2017
comment
Значение true, преобразованное в int, всегда дает 1, точка. Конечно, , если ваше условие просто правдиво, а не bool значение true, то это совсем другое дело. - person T.C.; 04.04.2017
comment
если вы не работаете на космическом шаттле, вы этого не сделаете, и даже тогда вы вероятно не сделаете - +1, и если производительность это критично, вы скорее всего, все равно пишу это в сборке .... - person JuSTMOnIcAjUSTmONiCAJusTMoNICa; 04.04.2017
comment
@nrocha этот конкретный компилятор (вероятно, большинство других) в конечном итоге перепишет код, чтобы вернуть (-condition) | 5, поэтому, если condition равно 0, он дает (-0) | 5 = 0, а если условие равно 1, то neg(1) = 11...11, поэтому or просто возвращает 5. Обратите внимание, что в ответе я предоставил ссылку где вы легко можете попробовать это сами. - person CompuChip; 05.04.2017
comment
@CompuChip - Очевидно, это вопрос о C / C ++, но есть ли другой ответ, если вы используете язык без компилятора, такой как python или Javascript? В случае интерпретации, делает ли интерпретатор наилучшую оптимизацию? - person EvSunWoodard; 05.04.2017
comment
Разве это не зависело бы от того, является ли условие в основном истинным или ложным. Я думаю, что ЦП любит предугадывать результат прыжка перед его выполнением. - person the_lotus; 05.04.2017
comment
Я думаю, что есть места, где это имеет значение больше, чем ракетостроение - высокочастотная торговля на форексе может быть одной из них? - person Tim; 06.04.2017

Ответ от CompuChip показывает, что для int они оба оптимизированы для одной и той же сборки, поэтому это не имеет значения.

Что, если значение - это матрица?

Я буду интерпретировать это в более общем смысле, т.е. что, если value относится к типу, конструкции и назначения которого дороги (а ходы дешевы).

тогда

T value = init1;
if (condition)
   value = init2;

является неоптимальным, потому что в случае, если condition истинно, вы выполняете ненужную инициализацию для init1, а затем выполняете копирование.

T value;
if (condition)
   value = init2;
else
   value = init3;

Это лучше. Но все же неоптимально, если построение по умолчанию дорого и если построение копии дороже, чем инициализация.

У вас есть хорошее решение с условным оператором:

T value = condition ? init1 : init2;

Или, если вам не нравится условный оператор, вы можете создать вспомогательную функцию, подобную этой:

T create(bool condition)
{
  if (condition)
     return {init1};
  else
     return {init2};
}

T value = create(condition);

В зависимости от того, что такое init1 и init2, вы также можете учитывать это:

auto final_init = condition ? init1 : init2;
T value = final_init;

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

person bolov    schedule 04.04.2017
comment
Дорогой и не оптимизирован. Например, если конструктор по умолчанию обнуляет матрицу, компилятор может понять, что присваивание просто перезаписывает эти нули, поэтому не обнуляет его вообще, а записывает непосредственно в эту память. Конечно, оптимизаторы привередливы, поэтому сложно предугадать, сработают они или нет ... - person Matthieu M.; 04.04.2017
comment
@MatthieuM. конечно. То, что я имел в виду под словом «дорого», дорого выполнять (по метрике, будь то частота процессора, использование ресурсов и т. Д.) Даже после оптимизации компилятора. - person bolov; 04.04.2017
comment
Мне кажется маловероятным, что строительство по умолчанию будет дорогим, а перемещение будет дешевым. - person plugwash; 04.04.2017
comment
@plugwash Рассмотрим класс с очень большим распределенным массивом. Конструктор по умолчанию выделяет и инициализирует массив, что требует больших затрат. Конструктор перемещения (не копирования!) Может просто поменять местами указатели с исходным объектом, и ему не нужно выделять или инициализировать большой массив. - person TrentP; 05.04.2017
comment
Поскольку части просты, я определенно предпочитаю использовать оператор ?:, а не вводить новую функцию. В конце концов, вы, скорее всего, не просто передадите в функцию условие, но и некоторые аргументы конструктора. Некоторые из них могут даже не использоваться create() в зависимости от состояния. - person cmaster - reinstate monica; 05.04.2017
comment
@cmaster Я видел (и был) по обе стороны от аргументов за и против ?:. В конечном счете, я думаю, это вопрос стиля, и на каждого, кто говорит, что вам следует использовать один, вы найдете другого, который скажет, что вам следует использовать другой. Аргумент, который немного изменил баланс для меня, заключается в том, что при отладке выбранная ветвь может быть идентифицирована быстрее в структуре if, чем в операторе ?:. - person bolov; 05.04.2017
comment
@MatthieuM. Если компилятор имеет только объявление этого конструктора из class, а обнуление выполняется в отдельно скомпилированном cpp, это очень маловероятная оптимизация. - person Kaz; 05.04.2017
comment
@Kaz: Что ж, сегодня с LTO все расплывчато, но я хочу сказать, что даже с полной информацией компилятор все равно может не выполнить оптимизацию. Если вычисления слишком сложны для этого, он просто выйдет из строя и сделает все возможное. - person Matthieu M.; 05.04.2017

На псевдо-ассемблере

    li    #0, r0
    test  r1
    beq   L1
    li    #1, r0
L1:

может или не может быть быстрее, чем

    test  r1
    beq   L1
    li    #1, r0
    bra   L2
L1:
    li    #0, r0
L2:

в зависимости от того, насколько сложен фактический процессор. От простейшего к самому причудливому:

  • Для любого ЦП, произведенного примерно после 1990 года, хорошая производительность зависит от соответствия кода в рамках кеш инструкций. Поэтому в случае сомнений минимизируйте размер кода. Это говорит в пользу первого примера.

  • С базовым "упорядоченным пятиступенчатым конвейером" процессором, который по-прежнему примерно такой во многих микроконтроллерах возникает пузырек конвейера при каждом ответвлении - условный или безусловный - берется, поэтому также важно минимизировать количество инструкций перехода. Это тоже говорит в пользу первого примера.

  • Несколько более сложные процессоры - достаточно модные, чтобы выполнять "вне очереди ", но недостаточно причудливым, чтобы использовать наиболее известные реализации этой концепции - могут возникать пузыри конвейера всякий раз, когда они сталкиваются с опасности записи после записи. Это говорит в пользу второго примера, где r0 записывается только один раз, несмотря ни на что. Эти процессоры обычно достаточно наворочены, чтобы обрабатывать безусловные переходы в сборщике инструкций, поэтому вы не просто обмениваете штраф за запись после записи на штраф за переход.

    Я не знаю, делает ли кто-нибудь еще такие процессоры. Однако процессоры, которые действительно используют "наиболее известные реализации" внепланового выполнения, скорее всего, будут сокращать количество менее часто используемых инструкций, поэтому вам нужно знать, что такого рода вещи может случиться. Реальный пример - ложные зависимости данных от регистров назначения в popcnt и lzcnt на Sandy Bridge. ЦП.

  • На самом высоком конце OOO-движок завершит выполнение одной и той же последовательности внутренних операций для обоих фрагментов кода - это аппаратная версия «не беспокойтесь об этом, компилятор в любом случае сгенерирует один и тот же машинный код». Однако размер кода по-прежнему имеет значение, и теперь вы также должны беспокоиться о предсказуемости условного перехода. Сбои предсказания ветвления потенциально могут вызвать полную очистку конвейера, что является катастрофическим за производительность; см. Почему быстрее обрабатывать отсортированный массив, а не несортированный?, чтобы понять, насколько это может изменить.

    Если ветвь крайне непредсказуема, а ваш ЦП имеет условные инструкции или инструкции условного перемещения, самое время их использовать:

        li    #0, r0
        test  r1
        setne r0
    

    or

        li    #0, r0
        li    #1, r2
        test  r1
        movne r2, r0
    

    Версия с условным множеством также более компактна, чем любая другая альтернатива; если эта инструкция доступна, она практически гарантированно будет правильной для этого сценария, даже если ветвление было предсказуемым. Версия с условным перемещением требует дополнительного рабочего регистра и всегда тратит впустую одну li инструкцию ресурсов диспетчеризации и выполнения; если ветвление действительно было предсказуемым, ветвистая версия вполне может быть быстрее.

person zwol    schedule 04.04.2017
comment
Я бы перефразировал ваше второе замечание относительно того, есть ли в ЦП механизм неисправности, который задерживается из-за опасностей записи после записи. Если у ЦП есть вышедший из строя механизм, который может справиться с такими опасностями без промедления, проблем нет, но также нет проблем, если у ЦП вообще нет неисправного механизма . - person supercat; 04.04.2017
comment
@supercat В конце абзаца рассматривается этот случай, но я подумаю, как сделать его более понятным. - person zwol; 04.04.2017
comment
Я не знаю, какие текущие процессоры имеют кеш, который заставил бы последовательно выполняемый код работать быстрее во второй раз, чем в первый раз (некоторые части ARM на основе флэш-памяти имеют интерфейс, который может буферизовать несколько строк флэш-данных, но могут извлекать код последовательно так же быстро, как они его выполняют, но ключ к быстрому запуску кода с тяжелыми ветвями - это копирование его в ОЗУ). ЦП без какого-либо нарушения порядка выполнения встречаются гораздо чаще, чем те, которые могут быть задержаны из-за опасности записи после записи. - person supercat; 04.04.2017
comment
Это очень проницательно - person Julien__; 13.04.2017

В неоптимизированном коде первый пример присваивает переменную всегда один раз, а иногда и дважды. Во втором примере переменная назначается только один раз. Условие одинаково для обоих путей кода, так что это не имеет значения. В оптимизированном коде это зависит от компилятора.

Как всегда, если вас это беспокоит, сгенерируйте сборку и посмотрите, что на самом деле делает компилятор.

person Neil    schedule 04.04.2017
comment
Если беспокоиться о производительности, они не будут компилировать неоптимизированные. Но, конечно, насколько хорош оптимизатор, зависит от компилятора / версии. - person old_timer; 04.04.2017
comment
AFAIK нет комментариев о том, какая архитектура компилятора / процессора и т.д., поэтому потенциально их компилятор не выполняет оптимизацию. Они могут компилироваться на чем угодно, от 8-битного PIC до 64-битного Xeon. - person Neil; 05.04.2017

Что заставит вас подумать, что любой из них, даже один лайнер, быстрее или медленнее?

unsigned int fun0 ( unsigned int condition, unsigned int value )
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{

    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
    value = condition ? 6 : 5;
    return(value);
}

Больше строк кода на языке высокого уровня дает компилятору больше возможностей для работы, поэтому, если вы хотите сформулировать общее правило, дайте компилятору больше кода для работы. Если алгоритм такой же, как и в приведенных выше случаях, можно ожидать, что компилятор с минимальной оптимизацией это выяснит.

00000000 <fun0>:
   0:   e3500000    cmp r0, #0
   4:   03a00005    moveq   r0, #5
   8:   13a00006    movne   r0, #6
   c:   e12fff1e    bx  lr

00000010 <fun1>:
  10:   e3500000    cmp r0, #0
  14:   13a00006    movne   r0, #6
  18:   03a00005    moveq   r0, #5
  1c:   e12fff1e    bx  lr

00000020 <fun2>:
  20:   e3500000    cmp r0, #0
  24:   13a00006    movne   r0, #6
  28:   03a00005    moveq   r0, #5
  2c:   e12fff1e    bx  lr

неудивительно, что первая функция выполнилась в другом порядке, хотя и с тем же временем выполнения.

0000000000000000 <fun0>:
   0:   7100001f    cmp w0, #0x0
   4:   1a9f07e0    cset    w0, ne
   8:   11001400    add w0, w0, #0x5
   c:   d65f03c0    ret

0000000000000010 <fun1>:
  10:   7100001f    cmp w0, #0x0
  14:   1a9f07e0    cset    w0, ne
  18:   11001400    add w0, w0, #0x5
  1c:   d65f03c0    ret

0000000000000020 <fun2>:
  20:   7100001f    cmp w0, #0x0
  24:   1a9f07e0    cset    w0, ne
  28:   11001400    add w0, w0, #0x5
  2c:   d65f03c0    ret

Надеюсь, вы поняли, что вы могли бы просто попробовать это, если бы не было очевидно, что разные реализации на самом деле не отличаются.

Что касается матрицы, не знаю, какое это имеет значение,

if(condition)
{
 big blob of code a
}
else
{
 big blob of code b
}

просто собираюсь поместить ту же оболочку if-then-else вокруг больших кляксов кода, будь то значение = 5 или что-то более сложное. Точно так же сравнение, даже если это большой кусок кода, его все равно нужно вычислить, и он равен или не равен чему-то часто компилируется с отрицанием, if (condition) do something часто компилируется, как если бы не условие goto.

00000000 <fun0>:
   0:   0f 93           tst r15     
   2:   03 24           jz  $+8         ;abs 0xa
   4:   3f 40 06 00     mov #6, r15 ;#0x0006
   8:   30 41           ret         
   a:   3f 40 05 00     mov #5, r15 ;#0x0005
   e:   30 41           ret         

00000010 <fun1>:
  10:   0f 93           tst r15     
  12:   03 20           jnz $+8         ;abs 0x1a
  14:   3f 40 05 00     mov #5, r15 ;#0x0005
  18:   30 41           ret         
  1a:   3f 40 06 00     mov #6, r15 ;#0x0006
  1e:   30 41           ret         

00000020 <fun2>:
  20:   0f 93           tst r15     
  22:   03 20           jnz $+8         ;abs 0x2a
  24:   3f 40 05 00     mov #5, r15 ;#0x0005
  28:   30 41           ret         
  2a:   3f 40 06 00     mov #6, r15 ;#0x0006
  2e:   30 41

мы только что проделали это упражнение с кем-то недавно на stackoverflow. Интересно, что этот компилятор mips не только понял, что функции были одинаковыми, но и заставил одну функцию просто переходить к другой, чтобы сэкономить место в коде. Не делал этого здесь, хотя

00000000 <fun0>:
   0:   0004102b    sltu    $2,$0,$4
   4:   03e00008    jr  $31
   8:   24420005    addiu   $2,$2,5

0000000c <fun1>:
   c:   0004102b    sltu    $2,$0,$4
  10:   03e00008    jr  $31
  14:   24420005    addiu   $2,$2,5

00000018 <fun2>:
  18:   0004102b    sltu    $2,$0,$4
  1c:   03e00008    jr  $31
  20:   24420005    addiu   $2,$2,5

еще несколько целей.

00000000 <_fun0>:
   0:   1166            mov r5, -(sp)
   2:   1185            mov sp, r5
   4:   0bf5 0004       tst 4(r5)
   8:   0304            beq 12 <_fun0+0x12>
   a:   15c0 0006       mov $6, r0
   e:   1585            mov (sp)+, r5
  10:   0087            rts pc
  12:   15c0 0005       mov $5, r0
  16:   1585            mov (sp)+, r5
  18:   0087            rts pc

0000001a <_fun1>:
  1a:   1166            mov r5, -(sp)
  1c:   1185            mov sp, r5
  1e:   0bf5 0004       tst 4(r5)
  22:   0204            bne 2c <_fun1+0x12>
  24:   15c0 0005       mov $5, r0
  28:   1585            mov (sp)+, r5
  2a:   0087            rts pc
  2c:   15c0 0006       mov $6, r0
  30:   1585            mov (sp)+, r5
  32:   0087            rts pc

00000034 <_fun2>:
  34:   1166            mov r5, -(sp)
  36:   1185            mov sp, r5
  38:   0bf5 0004       tst 4(r5)
  3c:   0204            bne 46 <_fun2+0x12>
  3e:   15c0 0005       mov $5, r0
  42:   1585            mov (sp)+, r5
  44:   0087            rts pc
  46:   15c0 0006       mov $6, r0
  4a:   1585            mov (sp)+, r5
  4c:   0087            rts pc

00000000 <fun0>:
   0:   00a03533            snez    x10,x10
   4:   0515                    addi    x10,x10,5
   6:   8082                    ret

00000008 <fun1>:
   8:   00a03533            snez    x10,x10
   c:   0515                    addi    x10,x10,5
   e:   8082                    ret

00000010 <fun2>:
  10:   00a03533            snez    x10,x10
  14:   0515                    addi    x10,x10,5
  16:   8082                    ret

и компиляторы

с этим кодом i можно было бы ожидать, что разные цели также будут совпадать

define i32 @fun0(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %. = select i1 %1, i32 6, i32 5
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
  %1 = icmp eq i32 %condition, 0
  %. = select i1 %1, i32 5, i32 6
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %2 = select i1 %1, i32 6, i32 5
  ret i32 %2
}


00000000 <fun0>:
   0:   e3a01005    mov r1, #5
   4:   e3500000    cmp r0, #0
   8:   13a01006    movne   r1, #6
   c:   e1a00001    mov r0, r1
  10:   e12fff1e    bx  lr

00000014 <fun1>:
  14:   e3a01006    mov r1, #6
  18:   e3500000    cmp r0, #0
  1c:   03a01005    moveq   r1, #5
  20:   e1a00001    mov r0, r1
  24:   e12fff1e    bx  lr

00000028 <fun2>:
  28:   e3a01005    mov r1, #5
  2c:   e3500000    cmp r0, #0
  30:   13a01006    movne   r1, #6
  34:   e1a00001    mov r0, r1
  38:   e12fff1e    bx  lr


fun0:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB0_2
    mov.w   #5, r15
.LBB0_2:
    pop.w   r4
    ret

fun1:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #5, r15
    cmp.w   #0, r12
    jeq .LBB1_2
    mov.w   #6, r15
.LBB1_2:
    pop.w   r4
    ret


fun2:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB2_2
    mov.w   #5, r15
.LBB2_2:
    pop.w   r4
    ret

Теперь технически существует разница в производительности в некоторых из этих решений, иногда результат 5, если перепрыгивают, результат равен 6 коду, и наоборот, ветвление быстрее, чем выполнение? можно было бы возразить, но исполнение должно быть разным. Но это больше похоже на условие if против if not в коде, в результате чего компилятор выполняет if, этот переход через else выполняется. но это не обязательно из-за стиля кодирования, а из-за сравнения и случаев if и the else в любом синтаксисе.

person old_timer    schedule 04.04.2017

Хорошо, поскольку сборка является одним из тегов, я просто предположу, что ваш код является псевдокодом (и не обязательно c), и переведу его человеком в сборку 6502.

1-й вариант (без остального)

        ldy #$00
        lda #$05
        dey
        bmi false
        lda #$06
false   brk

2-й вариант (с остальным)

        ldy #$00
        dey
        bmi else
        lda #$06
        sec
        bcs end
else    lda #$05
end     brk

Допущения: Условие находится в регистре Y, установите значение 0 или 1 в первой строке любого варианта, результат будет в аккумуляторе.

Итак, посчитав циклы для обеих возможностей каждого случая, мы видим, что первая конструкция обычно быстрее; 9 циклов, если условие равно 0, и 10 циклов, если условие равно 1, тогда как второй вариант также составляет 9 циклов, если условие равно 0, но 13 циклов, когда условие равно 1. (счетчики циклов не включают BRK в конце ).

Вывод: If only быстрее, чем If-Else construct.

И для полноты, вот оптимизированное value = condition + 5 решение:

ldy #$00
lda #$00
tya
adc #$05
brk

Это сокращает наше время до 8 циклов (опять же, не включая BRK в конце).

person Glen Yates    schedule 05.04.2017
comment
К сожалению для этого ответа, загрузка того же исходного кода в компилятор C (или в компилятор C ++) дает совершенно другой результат, чем передача его в мозг Глена. Нет никакой разницы, нет потенциала оптимизации между любыми альтернативами на уровне исходного кода. Просто используйте наиболее читаемый (предположительно, if / else). - person Quuxplusone; 06.04.2017
comment
@Ага. Компилятор либо оптимизирует оба варианта до самой быстрой версии, либо добавит дополнительные накладные расходы, которые намного перевешивают разницу между ними. Или оба. - person jpaugh; 06.04.2017
comment
Предполагая, что это не обязательно C, кажется разумным выбором, учитывая, что вопрос помечен как C ++ (к сожалению, не удается объявить типы задействованных переменных ). - person Toby Speight; 06.04.2017