Хто з них виконається швидше, якщо (прапор == 0) або якщо (0 == прапор)?


111

Питання для інтерв'ю: хто з них виконає швидше, if (flag==0)або if (0==flag)? Чому?


330
Висунута на найглупіше запитання про інтерв'ю. І є жорстка конкуренція.
Конрад Рудольф

119
Ви: Назвіть ситуацію, коли різниця між цими двома могла б вартувати. Інтерв'юер: Гаразд, вас найняли.
Кріс Лутц

37
Єдина відмінність між ними - це те, що з більш пізньою умовою ви застраховані від помилок, як if(flag = 0)за ціну трохи читабельності.
Amarghosh

22
@Amarghosh: Ціною, що робить ваш код важким для читання та неінтуїтивністю. Використовуйте попередні по черзі попередження компілятора, win-win.
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
}

Навіть якщо хтось не знає, як читати ІЧ, я вважаю, що це пояснює себе.


4
@Matthieu: ти сказав, що я ще не бачив правильної відповіді .. але моя правильна, я думаю: P
Nawaz

7
добре! Ваша відповідь можлива перетворює "найглухіше запитання" на "хитрі / найбідніші". "давайте викопаємо яму для кандидата і подивимось, чи потрапить він у нього ..." :) Я думаю, що всі ми автоматично припускаємо, що вони flagповинні бути цілими чи булевими. OTOH, що має змінну з іменем flagвизначеного користувачем типу, зовсім не так, IMHO
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

18
+1 - пройти додаткову милю, щоб довести, що оптимізація компілятора однакова.
k rey

56

У ваших версіях різниці не буде.

Я припускаю, що typeпрапор - це не визначений користувачем тип, скоріше це якийсь вбудований тип. Енум - виняток! . Ви можете ставитися до перегною, ніби вбудована. Насправді це значення є одним із вбудованих типів!

У випадку, якщо це визначений користувачем тип (крім enum), то відповідь повністю залежить від того, як ви перевантажили оператора ==. Зауважте, що вам слід перевантажуватись ==, визначаючи дві функції, по одній для кожної вашої версії!


8
це може бути єдиною можливою причиною задати це питання, ІМХО
davka

15
Я був би надзвичайно здивований, якби сучасні компілятори пропустили таку очевидну оптимізацію.
Педро д'Акіно

3
Наскільки мені відомо! це не побітна операція
Xavier Combelle

8
@Nawaz: не спровокував, але ваша відповідь фактично неправильна, і це жахливо, що все одно отримано так багато відгуків. Для запису порівняння цілого числа з 0 - це одна інструкція по збірці , повністю нарівні з запереченням. Насправді, якщо компілятор трохи дурний, то це може бути навіть швидше, ніж заперечення (хоч це ймовірно).
Конрад Рудольф

6
@Nawaz: все ще неправильно говорити, що це може, буде, або, як правило, буде швидше. Якщо є різниця, версія "порівняння проти нуля" буде швидшою, оскільки заперечення дійсно перекладається на дві операції: "заперечити операнд; перевірити, чи є результат ненульовим". На практиці, звичайно, компілятор оптимізує його, щоб отримати той самий код, що і звичайна версія "порівняти з нулем", але оптимізація застосовується до версії заперечення, щоб змусити її наздогнати, а не навпаки. Конрад правий.
jalf

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 Huh. Я, мабуть, пропустив пост з мета, що описує "правильний" стиль підкреслення.
Лінус Клін

7
Я взагалі не голосував, але для чого це варто: чому так важливо, щоб люди пояснювали себе, коли вони голосували? Голоси анонімні за дизайном. Я цілком проти ідеї, що низохідні завжди повинні коментувати, тому що я особисто ніколи не хочу вважати себе породним лише тому, що залишив коментар, вказуючи на проблему. Можливо, низохід вважав, що більшість відповідей не стосується питання швидкості? Можливо, він вважав, що це стимулює стиль кодування, який він не схвалює? Можливо, він був хуй і хотів, щоб його власна відповідь мала найвищу оцінку?
Девід Хедлунд

3
Люди повинні мати право вільно голосувати незалежно від причини. Зважаючи на репутацію, це майже завжди є хорошою справою, оскільки вона часто провокує інших людей, щоб протистояти незаслуженому голосному голосуванню, коли насправді одне оновлення скасувало б п'ять незаслужених подій.
Девід Хедлунд

26
@David: Downvoters повинні пояснити себе, оскільки цей сайт не стосується таємних бюлетенів про популярність, анонімне голосування тощо. Цей сайт про навчання. Якщо хтось каже, що відповідь невірна, зволікаючи її, низовик виявляється егоїстичним своїми знаннями, якщо вони не пояснюють, чому. Вони готові взяти на себе всі заслуги, коли мають рацію, але не бажають ділитися знаннями, коли інші помиляються.
Джон Дайблінг

1
Тільки для того, щоб усунути проблему стилю підкреслення, я дійсно думаю, що Матьє задумав це як жарт. Я був би здивований, побачивши, що хтось віддає свої голоси залежно від таких питань. Сказавши це, не всі користуються голосами точно однаково. Я можу побачити обґрунтування спростування, оскільки, схоже, публікація виступає за стиль кодування, який виборця можуть не схвалити (зверніть увагу на різницю між пропагуванням стилю кодування - "якщо ви напишете свій код таким чином, ви отримаєте помилку компілятора, коли ви зробите цей друк "- і просто використання стилю кодування, наприклад, дужки). У цьому ...
Девід Хедлунд

16

Різниця в швидкості абсолютно не буде. Чому це повинно бути?


7
якщо компілятор був повністю відсталим. Це єдина причина.
JeremyP

@JeremyP: Я не уявляю різниці, навіть якщо компілятор був затримкою. Наскільки письменник-упорядник повинен був зробити це спеціально , наскільки я можу сказати.
Джон

2
Якщо припустити, що процесор має інструкцію "test if 0", він x == 0може використовувати його, але 0 == xможе використовувати звичайне порівняння. Я сказав, що це доведеться відставати.
JeremyP

8
Якщо прапор - це визначений користувачем тип із асиметричним перевантаженням оператора == ()
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 == с) викликається оператор перетворення, а потім повертається результат порівнюється з 0. У другому випадку викликається оператор ==.


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

11

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


2
Що не так у бенчмаркінгу? інколи практика говорить вам більше, ніж теорія
Ельзо Валугі

1
Це відповідь, яку я шукав, коли почав читати цю тему. Здається, що теорія є більш привабливою, ніж практика, шукаючи відповіді та оновлення :)
Самуель Рівас

як він міг орієнтуватися на співбесіді? плюс я думаю, що інтерв'юер навіть не знає, що означає бенчмаркінг, тому він міг образитися.
IAdapter

Вони правильно відповідають на запитання (IMO): "Це дуже залежить від компілятора та решти програми. Я б написав орієнтир і перевірив його за 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, наприклад, не попередить у випадку призначення в умовних умовах.
Лінус Клін

5

Як говорили інші, різниці немає.

0має бути оцінено. flagмає бути оцінено. Цей процес займає той же час, незалежно від того, на яку сторону вони розміщені.

Правильною відповіддю було б: вони обоє з однаковою швидкістю.

Навіть вирази if(flag==0)і if(0==flag)мають однакову кількість символів! Якби одна з них була написана як if(flag== 0), тоді у компілятора було б ще один пробіл для розбору, тож у вас буде законний привід вказати час компіляції.

Але оскільки такого немає, абсолютно немає причин, чому слід бути швидшим за інших. Якщо є причина, компілятор робить якісь дуже, дуже дивні речі для згенерованого коду ...


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);
}

5

Ну, я повністю згоден з усім, що було сказано в коментарях до ОП, заради вправ:

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

Все-таки я не хотів би працювати на начальника, який думає в цих умовах ...


4

Безумовно, різниці в швидкості виконання. Стан необхідно оцінювати в обох випадках однаково.


3

Я думаю, що найкраща відповідь - "на якій мові цей приклад"?

Питання не вказувало мову, і вона позначена як "C" та "C ++". Точна відповідь потребує додаткової інформації.

Це запитання щодо програмування, але це може бути корисним у недоброзичливому підрозділі "давайте опитуваному достатньо мотузки, щоб або повісити себе, або побудувати гойдалку на дереві". Проблема таких питань полягає в тому, що вони, як правило, записуються і передаються від інтерв'юера до інтерв'юера, поки не потраплять до людей, які насправді не розуміють цього з усіх кутів.


3

Побудуйте дві прості програми, використовуючи запропоновані способи.

Зберіть коди. Подивіться на збори, і ви можете судити, але я сумніваюся, що є різниця!

Інтерв'ю стає нижчим, ніж будь-коли.


2

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


0

Якщо взагалі була різниця, що зупиняє компілятор вибирати швидший один раз? Отже, логічно, різниці не може бути. Напевно, саме так очікує інтерв'юер. Це насправді геніальне питання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.