Какой из них будет выполняться быстрее, if (flag == 0) или if (0 == flag)?

111

Вопрос на собеседовании: какой из них будет выполняться быстрее if (flag==0)или if (0==flag)? Зачем?

Вишванатх Далви
источник
330
Номинация на самый глупый вопрос на интервью. И есть жесткая конкуренция.
Конрад Рудольф
119
Вы: Назовите ситуацию, при которой, возможно, стоит подумать о разнице между этими двумя понятиями. Интервьюер: Хорошо, вас наняли.
Крис Лутц
37
Единственная разница между ними заключается в том, что с более поздним соглашением вы застрахованы от ошибок, например, if(flag = 0)ценой небольшой читабельности.
Amarghosh
22
@Amarghosh: Ценой того, что ваш код становится трудным для чтения и не интуитивно понятным. Используйте первое при включении предупреждений компилятора, беспроигрышный вариант.
GManNickG
129
Однажды автор компилятора узнал об этом в своем интервью. Он прошептал в ответ: «Кто из них вы хотите быть быстрее?».

Ответы:

236

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

Ответ:

Какой flagтип?

В том случае, если это flagдействительно пользовательский тип. Тогда это зависит от того, какая перегрузка operator==выбрана. Конечно, может показаться глупым, что они не будут симметричными, но это, безусловно, разрешено, и я уже видел другие нарушения.

Если flagэто встроенный, то оба должны иметь одинаковую скорость.

Из статьи Википедии о x86, я бы поставил на Jxxинструкцию для ifзаявления: возможно, JNZ(переход , если не ноль) или некоторый эквивалент.

Сомневаюсь, что компилятор пропустит такую ​​очевидную оптимизацию даже при отключенной оптимизации. Это тот тип вещей, для которого предназначена Peephole Optimization .

EDIT: снова возник, поэтому давайте добавим некоторую сборку (LLVM 2.7 IR)

int regular(int c) {
  if (c == 0) { return 0; }
  return 1;
}

int yoda(int c) {
  if (0 == c) { return 0; }
  return 1;
}

define i32 @regular(i32 %c) nounwind readnone {
entry:
  %not. = icmp ne i32 %c, 0                       ; <i1> [#uses=1]
  %.0 = zext i1 %not. to i32                      ; <i32> [#uses=1]
  ret i32 %.0
}

define i32 @yoda(i32 %c) nounwind readnone {
entry:
  %not. = icmp ne i32 %c, 0                       ; <i1> [#uses=1]
  %.0 = zext i1 %not. to i32                      ; <i32> [#uses=1]
  ret i32 %.0
}

Даже если кто-то не умеет читать IR, я думаю, что это говорит само за себя.

Матье М.
источник
4
@Matthieu: ты сказал, что я еще не видел правильного ответа .. но мой правильный, я думаю: P
Nawaz
7
хорошо! ваш возможный ответ превращает "самый глупый вопрос" в "уловки / самые подлые". «давай выкопаем яму для кандидата и посмотрим, упадет ли он в нее ...» :) Я думаю, мы все автоматически предполагаем, что это flagдолжно быть целое или логическое значение. OTOH, наличие переменной с именем flagопределяемого пользователем типа совершенно неправильно само по себе, ИМХО
davka
@Nawaz: Возможно, я пропустил последний абзац вашего ответа: p
Матье М.
1
@Nawaz: На самом деле я не участвую в гонках, я обычно читаю вопросы спустя много времени после того, как на них были даны ответы, и люди, как правило, читают только первые наиболее набранные ответы :) Но на самом деле я читаю материал об оптимизации компилятора, и это поразило меня как типичный случай тривиальной оптимизации, поэтому я подумал, что укажу на него для тех читателей, которые действительно беспокоятся ... Я очень удивлен, что на самом деле я получил так много голосов. Теперь это мой ответ, получивший наибольшее количество голосов, хотя это, конечно, не тот, к которому я приложил больше всего усилий: / В любом случае я отредактировал свой ответ и исправил свое утверждение :)
Матье М.
2
@mr_eclair: встроенный тип - это тип, который (как следует из названия) встроен в язык. То есть он доступен даже без единой #includeдирективы. Для простоты обычно составляетint , char, boolи тому подобное. Все остальные типы называются определенные пользователем, то есть они существуют , потому что они являются результатом какого - то пользователя объявляющего их: typedef, enum, struct, class. Например, std::stringопределяется пользователем, хотя вы, конечно, не определили его сами :)
Matthieu M.
56

Тот же код для amd64 с GCC 4.1.2:

        .loc 1 4 0  # int f = argc;
        movl    -20(%rbp), %eax
        movl    %eax, -4(%rbp)
        .loc 1 6 0 # if( f == 0 ) {
        cmpl    $0, -4(%rbp)
        jne     .L2
        .loc 1 7 0 # return 0;
        movl    $0, -36(%rbp)
        jmp     .L4
        .loc 1 8 0 # }
 .L2:
        .loc 1 10 0 # if( 0 == f ) {
        cmpl    $0, -4(%rbp)
        jne     .L5
        .loc 1 11 0 # return 1;
        movl    $1, -36(%rbp)
        jmp     .L4
        .loc 1 12 0 # }
 .L5:
        .loc 1 14 0 # return 2;
        movl    $2, -36(%rbp)
 .L4:
        movl    -36(%rbp), %eax
        .loc 1 15 0 # }
        leave
        ret
skc
источник
18
+1 за то, что приложил все усилия, чтобы доказать, что оптимизация компилятора такая же.
k rey
56

В ваших версиях разницы не будет.

Я предполагаю, что typeфлаг of не является определяемым пользователем типом, а скорее является встроенным типом. Enum - исключение! . Вы можете относиться к перечислению как к встроенному. Фактически, значения it - это один из встроенных типов!

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

Наваз
источник
8
это могло быть единственной возможной причиной задать этот вопрос, ИМХО
davka
15
Я был бы крайне удивлен, если бы современные компиляторы пропустили такую ​​очевидную оптимизацию.
Педро д'Акино
3
Насколько мне известно ! не является побитовой операцией
Ксавье Комбель 07
8
@Nawaz: не голосовал против, но ваш ответ на самом деле неверен, и ужасно, что он получил столько голосов. Для записи сравнение целого числа с 0 - это единственная инструкция ассемблера , полностью равная отрицанию. На самом деле, если компилятор немного глуп, это может быть даже быстрее, чем отрицание (хотя маловероятно).
Конрад Рудольф
6
@Nawaz: все еще неправильно говорить, что он может, будет или обычно будет быстрее. Если есть различие, то версия «сравнить с нулем» будет быстрее, так как отрицательная единица действительно переводится в две операции: «отрицать операнд; проверить, что результат не равен нулю». На практике, конечно, компилятор оптимизирует его, чтобы получить тот же код, что и простая версия «сравнить с нулем», но оптимизация применяется к версии с отрицанием, чтобы догнать ее, а не наоборот. Конрад прав.
jalf 07
27

Абсолютно никакой разницы.

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

if (flag = 0)  // typo here
   {
   // code never executes
   }

if (0 = flag) // typo and syntactic error -> compiler complains
   {
   // ...
   }

Хотя верно то, что, например, C-компилятор предупреждает в случае первого ( flag = 0), таких предупреждений нет в PHP, Perl или Javascript или <insert language here>.

Линус Клин
источник
@Matthieu Ха. Я, должно быть, пропустил пост на мета, описывающий "правильный" стиль крепления.
Линус Клин 07
7
Я вообще не голосовал, но это того стоит: почему так важно, чтобы люди объяснялись каждый раз, когда они голосуют? Голосование анонимно по дизайну. Я категорически против того, чтобы те, кто проголосовал против, всегда оставляли комментарии, потому что я лично никогда не хочу, чтобы меня считали проигравшим только потому, что я оставил комментарий, указывающий на проблему. Возможно, тот, кто проголосовал против, думал, что большая часть ответа не имеет отношения к вопросу о скорости? Возможно, он думал, что это поощряет стиль программирования, который он не одобрял? Возможно, он был придурком и хотел, чтобы его ответ имел наивысший рейтинг?
Дэвид Хедлунд
3
Люди должны иметь право голосовать по своему желанию, независимо от причины. С точки зрения репутации это почти всегда хорошо, так как это часто провоцирует других людей на голосование за, противодействие незаслуженному голосованию против, тогда как на самом деле один голос за отменяет пять незаслуженных голосов против.
Дэвид Хедлунд
26
@David: Противники должны объясниться, потому что этот сайт не о секретных бюллетенях о популярности, анонимном голосовании и т.п. Этот сайт посвящен обучению. Если кто-то говорит, что ответ неверен, голосуя против, это значит, что он эгоистично относится к своим знаниям, если не объясняет почему. Они готовы считать себя правыми, но не готовы делиться знаниями, когда другие ошибаются.
Джон Диблинг
1
Я действительно думаю, что Матье задумал это как шутку, просто чтобы решить проблему со стилем крепления. Я был бы удивлен, увидев, что кто-то голосует в зависимости от таких вопросов. При этом не все используют голоса одинаково. Я мог видеть обоснование для голосования против, потому что сообщение, похоже, защищает стиль кодирования, который избиратель может не одобрить (обратите внимание на разницу между защитой стиля кодирования - «если вы напишете свой код таким образом, вы получите ошибку компилятора, когда сделаете эта опечатка »- и просто с использованием стиля кодирования, такого как фигурные скобки). В этом ...
Дэвид Хедлунд
16

По скорости абсолютно никакой разницы не будет. Почему должно быть?

Джон
источник
7
если компилятор был полностью отсталым. Это единственная причина.
JeremyP
@JeremyP: Я не могу представить разницу, даже если бы компилятор был отсталым. Насколько я могу судить, автору компилятора пришлось бы делать это специально .
Джон
2
Предполагая, что у процессора есть инструкция «проверить, если 0», x == 0можно использовать ее, но 0 == xможно использовать обычное сравнение. Я действительно сказал, что его нужно будет отложить.
JeremyP 07
8
Если flag является определяемым пользователем типом с асимметричной перегрузкой operator == ()
OrangeDog
Потому что у нас может быть virtual operator==(int)пользовательский тип?
lorro
12

Ну, есть разница, когда флаг является определяемым пользователем типом

struct sInt
{
    sInt( int i ) : wrappedInt(i)
    {
        std::cout << "ctor called" << std::endl;
    }

    operator int()
    {
        std::cout << "operator int()" << std::endl;
        return wrappedInt;
    }

    bool operator==(int nComp)
    {
        std::cout << "bool operator==(int nComp)" << std::endl;
        return (nComp == wrappedInt);
    }

    int wrappedInt;
};

int 
_tmain(int argc, _TCHAR* argv[])
{
    sInt s(0);

    //in this case this will probably be faster
    if ( 0 == s )
    {
        std::cout << "equal" << std::endl;
    }

    if ( s == 0 )
    {
        std::cout << "equal" << std::endl;
    }
}

В первом случае (0 == s) вызывается оператор преобразования, а затем возвращаемый результат сравнивается с 0. Во втором случае вызывается оператор ==.

ds27680
источник
3
+1 за упоминание о том, что оператор преобразования может иметь такое же значение, как и оператор ==.
Тони Делрой
11

Если сомневаетесь, сравните его и узнайте правду.

Эльзо Валуги
источник
2
что не так с бенчмаркингом? иногда практика говорит вам больше, чем теория
Эльзо Валуги
1
Это ответ, который я искал, когда начал читать эту ветку. Кажется, что теория более привлекательна, чем практика, если посмотреть ответы и проголосовать :)
Сэмюэл Ривас
как он мог сравнивать себя на собеседовании? к тому же я думаю, что интервьюер даже не знает, что такое бенчмаркинг, поэтому он мог обидеться.
IAdapter
Они правильно ответили на вопрос (ИМО): «Это в значительной степени зависит от компилятора и остальной программы. Я бы написал тест и протестировал его за 5 минут»
Сэмюэл Ривас
7

Они должны быть точно такими же по скорости.

Обратите внимание, однако, что некоторые люди помещают константу слева при сравнении равенства (так называемые «условия Йоды»), чтобы избежать всех ошибок, которые могут возникнуть, если вы напишете =(оператор присваивания) вместо ==(оператор сравнения равенства); поскольку присвоение литералу вызывает ошибку компиляции, ошибки такого рода можно избежать.

if(flag=0) // <--- typo: = instead of ==; flag is now set to 0
{
    // this is never executed
}

if(0=flag) // <--- compiler error, cannot assign value to literal
{

}

С другой стороны, большинство людей находят «условные выражения Йоды» странно выглядящими и раздражающими, особенно потому, что класс ошибок, которые они предотвращают, можно определить также с помощью соответствующих предупреждений компилятора.

if(flag=0) // <--- warning: assignment in conditional expression
{

}
Маттео Италия
источник
Спасибо за отклик. Однако обратите внимание, что PHP, например, не будет предупреждать в случае присваивания в условных выражениях.
Линус Клин 07
5

Как говорили другие, разницы нет.

0 должен быть оценен. flagдолжен быть оценен. Этот процесс занимает одинаковое время, независимо от того, с какой стороны они расположены.

Правильный ответ: у них одинаковая скорость.

Даже выражения if(flag==0)и if(0==flag)имеют одинаковое количество символов! Если бы один из них был написан какif(flag== 0) , тогда у компилятора было бы одно дополнительное пространство для синтаксического анализа, так что у вас была бы законная причина указать время компиляции.

Но поскольку такого нет, нет абсолютно никаких причин, по которым один должен быть быстрее другого. Если есть причина, то компилятор делает очень и очень странные вещи с сгенерированным кодом ...

darioo
источник
5

Какой из них быстрый, зависит от того, какую версию == вы используете. Вот фрагмент, в котором используются 2 возможные реализации ==, и в зависимости от того, выберете ли вы вызов x == 0 или 0 == x, будет выбран один из 2 вариантов.

Если вы просто используете POD, это не имеет значения, когда дело касается скорости.

#include <iostream>
using namespace std;

class x { 
  public:
  bool operator==(int x) { cout << "hello\n"; return 0; }
  friend bool operator==(int x, const x& a) { cout << "world\n"; return 0; } 
};

int main()
{ 
   x x1;
   //int m = 0;
   int k = (x1 == 0);
   int j = (0 == x1);
}
Фанатик23
источник
5

Что ж, я полностью согласен со всем, что сказано в комментариях к OP, ради упражнения:

Если компилятор недостаточно умен (действительно, вы не должны его использовать) или оптимизация отключена, x == 0можно скомпилировать в собственную jump if zeroинструкцию сборки , в то время как0 == x может быть более общее (и дорогостоящее) сравнение числовых значений.

Тем не менее, я бы не хотел работать на начальника, который думает так ...

DAVKA
источник
4

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

Сачин Шанбхаг
источник
3

Думаю, лучший ответ - «на каком языке этот пример»?

В вопросе не указан язык, и он помечен как «C», так и «C ++». Для точного ответа требуется дополнительная информация.

Это паршивый вопрос программирования, но он мог бы быть хорошим для хитрого отдела «давайте дадим интервьюируемому достаточно веревки, чтобы он повесился или построил качели для дерева». Проблема с такими вопросами в том, что они обычно записываются и передаются от интервьюера к интервьюеру, пока не дойдут до людей, которые действительно не понимают его со всех сторон.

Марш Рэй
источник
3

Постройте две простые программы, используя предложенные способы.

Соберите коды. Посмотри на сборку и можешь судить, но я сомневаюсь, что есть разница!

Интервью становится все меньше, чем когда-либо.

Ошибка синтаксиса
источник
2

В качестве отступления (я действительно думаю, что любой достойный компилятор сделает этот вопрос спорным, поскольку он оптимизирует его), использование флага 0 == вместо флага == 0 предотвращает опечатку, когда вы забываете один из = (т.е. если вы случайно набираете flag = 0 он будет компилироваться, но 0 = flag не будет), что я считаю ошибкой, которую каждый совершал в тот или иной момент ...

Kindread
источник
0

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

балки
источник