Który z nich wykona się szybciej, jeśli (flaga == 0) czy (0 == flaga)?

111

Pytanie do wywiadu: Który z nich zadziała szybciej, if (flag==0)czy if (0==flag)? Czemu?

Vishwanath Dalvi
źródło
330
Nominowany za najgłupsze pytanie w historii wywiadu. I jest ostra konkurencja.
Konrad Rudolph,
119
Ty: Wymień sytuację, w której różnica między tymi dwoma może być warta uwagi. Prowadzący: Dobra, jesteś zatrudniony.
Chris Lutz,
37
Jedyna różnica między nimi polega na tym, że przy późniejszej konwencji jesteś ubezpieczony od błędów, na przykład if(flag = 0)za cenę niewielkiej czytelności.
Amarghosh,
22
@Amarghosh: Kosztem tego, że kod jest trudny do odczytania i nieintuicyjny. Użyj tego pierwszego po kolei, aby włączyć ostrzeżenia kompilatora, wygrana-wygrana.
GManNickG,
129
Kiedyś pisarz kompilatora dostał to w swoim wywiadzie. Wyszeptał w odpowiedzi, „który z nich zrobić ty chcesz być szybciej?”.

Odpowiedzi:

236

Nie widziałem jeszcze żadnej poprawnej odpowiedzi (a jest już kilka) zastrzeżenie: Nawaz zwrócił uwagę na pułapkę zdefiniowaną przez użytkownika . I żałuję, że pośpiesznie oddałem głos za "najgłupsze pytanie", ponieważ wydaje się, że wielu nie odpowiedziało dobrze i daje to miejsce na miłą dyskusję na temat optymalizacji kompilatora :)

Odpowiedź to:

Jaki jest flagtyp?

W przypadku, gdy flagfaktycznie jest to typ zdefiniowany przez użytkownika. To zależy od tego, które przeciążenie operator==jest wybrane. Oczywiście może wydawać się głupie, że nie byłyby symetryczne, ale z pewnością jest to dozwolone, a widziałem już inne nadużycia.

Jeśli flagjest wbudowany, oba powinny mieć tę samą prędkość.

Z Wikipedii sprawie x86, założę się o Jxxinstrukcje do ifstwierdzenia: być może JNZ(Przełącz jeśli nie zero) lub jakiś odpowiednik.

Wątpię, by kompilator pominął tak oczywistą optymalizację, nawet przy wyłączonych optymalizacjach. To jest rodzaj rzeczy, dla których jest przeznaczona Optymalizacja Peephole .

EDYCJA: Znowu wyskoczył, więc dodajmy trochę assemblacji (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
}

Nawet jeśli nie umie się czytać IR, myślę, że jest to oczywiste.

Matthieu M.
źródło
4
@Matthieu: powiedziałeś , że nie widziałem jeszcze żadnej poprawnej odpowiedzi ... ale moja jest poprawna, myślę, że: P
Nawaz
7
dobry! Twoja możliwa odpowiedź zamienia „najgłupsze pytanie” na „najtrudniejsze / najgorsze”. „Wykopmy dziurę dla kandydata i zobaczmy, czy wpadnie w to…” :) Myślę, że wszyscy automatycznie zakładamy, że flagmusi to być liczba całkowita lub wartość logiczna. OTOH, posiadanie zmiennej o nazwie flagtypu zdefiniowanego przez użytkownika jest dość złe samo w sobie, IMHO
davka
@Nawaz: Mogłem pominąć ostatni akapit twojej odpowiedzi: p
Matthieu M.
1
@Nawaz: Tak naprawdę nie ścigam się, zwykle czytam pytania długo po tym, jak otrzymałem na nie odpowiedź, a ludzie czytają tylko pierwsze najbardziej pozytywne odpowiedzi :) Ale tak naprawdę czytam rzeczy o optymalizacjach kompilatora i wydało mi się to typowy przypadek trywialnej optymalizacji, więc pomyślałem, że wskazałbym to tym czytelnikom, którzy się tym przejmują ... Jestem bardzo zaskoczony, że dostałem tak wiele głosów za. Teraz jest to moja najczęściej głosowana odpowiedź, chociaż na pewno nie ta, w którą wkładam najwięcej wysiłku: / W każdym razie zredagowałem odpowiedź i poprawiłem zdanie :)
Matthieu M.
2
@mr_eclair: typ wbudowany to typ, który jest (jak sugeruje nazwa) wbudowany w język. Oznacza to, że jest dostępny nawet bez jednej #includedyrektywy. Dla uproszczenia, to zwykle wynosi int, char, booli tym podobne. Wszystkie pozostałe rodzaje Mówi się, że zdefiniowane przez użytkownika, to one istnieją, ponieważ są one wynikiem jakiegoś użytkownik je uznającej: typedef, enum, struct, class. Na przykład, std::stringjest zdefiniowany przez użytkownika, nawet jeśli na pewno nie zdefiniowałeś go sam :)
Matthieu M.
56

Ten sam kod dla amd64 z 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
źródło
18
+1 za pójście o krok dalej i udowodnienie, że optymalizacja kompilatora jest taka sama.
k rey
56

W twoich wersjach nie będzie różnicy.

Zakładam, że typeflaga of nie jest typem zdefiniowanym przez użytkownika, a raczej typem wbudowanym. Enum jest wyjątkiem! . Wyliczenie można traktować tak, jakby było wbudowane. W rzeczywistości to wartości są jednym z wbudowanych typów!

W przypadku, jeśli jest to typ zdefiniowany przez użytkownika (z wyjątkiem enum), odpowiedź całkowicie zależy od tego, jak przeciążono operator ==. Zauważ, że musisz przeciążać ==, definiując dwie funkcje, po jednej dla każdej wersji!

Nawaz
źródło
8
to może być jedyny możliwy powód do zadawania tego pytania, IMHO
davka
15
Byłbym bardzo zaskoczony, gdyby współczesnym kompilatorom brakowało tak oczywistej optymalizacji.
Pedro d'Aquino
3
Zgodnie z moją wiedzą ! nie jest operacją bitową
Xavier Combelle
8
@Nawaz: nie przegłosowałem, ale Twoja odpowiedź jest nieprawdziwa i to okropne, że wciąż otrzymała tak wiele pozytywnych głosów. Dla przypomnienia, porównanie liczby całkowitej do 0 jest pojedynczą instrukcją asemblacyjną , całkowicie na równi z negacją. W rzeczywistości, jeśli kompilator jest trochę głupi, może to być nawet szybsze niż negacja (choć mało prawdopodobne).
Konrad Rudolph,
6
@Nawaz: nadal błędem jest twierdzenie, że może, będzie lub zazwyczaj będzie działać szybciej. Jeśli jest różnica, to wersja „porównaj z zerem” będzie szybsza, ponieważ negacja tak naprawdę przekłada się na dwie operacje: „negacja argumentu; sprawdź, czy wynik jest różny od zera”. W praktyce, oczywiście, kompilator optymalizuje go, aby uzyskać ten sam kod, co zwykła wersja „porównaj z zerem”, ale optymalizacja jest stosowana do wersji negacji, aby nadrobić zaległości, a nie na odwrót. Konrad ma rację.
jalf
27

Nie ma absolutnie żadnej różnicy.

Możesz zyskać punkty, odpowiadając na to pytanie podczas rozmowy kwalifikacyjnej, odwołując się do eliminacji literówek przy przypisaniu / porównaniach, chociaż:

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

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

Chociaż prawdą jest, że np. Kompilator C ostrzega w przypadku poprzedniej ( flag = 0), nie ma takich ostrzeżeń w PHP, Perl, Javascript lub <insert language here>.

Linus Kleen
źródło
@Matthieu Huh. Musiałem przegapić post na temat meta opisujący „właściwy” styl usztywnienia.
Linus Kleen,
7
W ogóle nie głosowałem, ale po co jest to warte: dlaczego jest tak ważne, aby ludzie tłumaczyli się, kiedy oddają głos? Głosy są anonimowe z założenia. Całkowicie sprzeciwiam się idei, że osoby osłabiające powinny zawsze komentować, ponieważ osobiście nigdy nie chcę być uznawany za przeciwnika tylko dlatego, że zostawiłem komentarz wskazujący na problem. Być może przeciwnik uważał, że większość odpowiedzi nie ma związku z pytaniem o szybkość? Może pomyślał, że zachęca to do stylu kodowania, którego nie aprobował? Może był kutasem i chciał, aby jego własna odpowiedź miała najwyższą ocenę?
David Hedlund,
3
Ludzie powinni mieć swobodę głosowania, jak chcą, bez względu na powód. Jeśli chodzi o reputację, jest to prawie zawsze dobra rzecz, ponieważ często prowokuje innych ludzi do głosowania za, przeciwstawienia się niezasłużonemu głosowi przeciw, podczas gdy w rzeczywistości pojedynczy głos za anulowałby pięć niezasłużonych głosów przeciw.
David Hedlund
26
@David: Downvoters powinni się wytłumaczyć, ponieważ ta strona nie dotyczy tajnych głosowań popularności, anonimowego głosowania lub tym podobnych. Ta strona dotyczy nauki. Jeśli ktoś mówi, że odpowiedź jest niepoprawna, odrzucając ją, przeciwnik jest samolubny ze swoją wiedzą, jeśli nie wyjaśni dlaczego. Są gotowi przyjąć całą zasługę za to, że mają rację, ale nie chcą dzielić się wiedzą, gdy inni się mylą.
John Dibling
1
Żeby usunąć problem ze stylem usztywniania, naprawdę myślę, że Matthieu zamierzał to jako żart. Zdziwiłbym się, gdyby ktoś głosował w takich sprawach. Mimo to nie wszyscy używają głosów w dokładnie taki sam sposób. Mogłem zobaczyć uzasadnienie głosowania w dół, ponieważ post wydaje się propagować styl kodowania, którego wyborca ​​może nie zaakceptować (zwróć uwagę na różnicę między zalecaniem stylu kodowania - „jeśli napiszesz swój kod w ten sposób, otrzymasz błąd kompilatora ta literówka ”- i po prostu używając stylu kodowania, takiego jak nawiasy klamrowe) W tym ...
David Hedlund,
16

Nie będzie żadnej różnicy pod względem prędkości. Dlaczego miałoby być?

Jon
źródło
7
jeśli kompilator był całkowicie opóźniony. To jedyny powód.
JeremyP,
@JeremyP: Nie mogę sobie wyobrazić różnicy, nawet jeśli kompilator był opóźniony. O ile wiem, autor kompilatora musiałby to zrobić celowo .
Jon
2
Zakładając, że procesor ma instrukcję „test, jeśli 0”, x == 0może jej użyć, ale 0 == xmoże użyć normalnego porównania. Powiedziałem, że to będzie musiało być opóźnione.
JeremyP,
8
Jeśli flaga jest typem zdefiniowanym przez użytkownika z asymetrycznym przeciążeniem operatora == ()
OrangeDog,
Bo może mieć virtual operator==(int)typu zdefiniowanego przez użytkownika?
lorro
12

Cóż, jest różnica, gdy flaga jest typem zdefiniowanym przez użytkownika

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

W pierwszym przypadku (0 == s) wywoływany jest operator konwersji, a następnie zwracany wynik jest porównywany z 0. W drugim przypadku wywoływany jest operator ==.

ds27680
źródło
3
+1 za wzmiankę, że operator konwersji może być tak samo trafny jak operator ==.
Tony Delroy,
11

W razie wątpliwości sprawdź to i poznaj prawdę.

Elzo Valugi
źródło
2
co jest nie tak z benchmarkingiem? czasami praktyka mówi więcej niż teoria
Elzo Valugi
1
Oto odpowiedź, której szukałem, kiedy zacząłem czytać ten wątek. Wydaje się, że teoria jest bardziej atrakcyjne niż praktyce szuka odpowiedzi i upvotes :)
Samuel Rivas
jak mógł porównać wyniki podczas rozmowy kwalifikacyjnej? a ponadto myślę, że osoba przeprowadzająca wywiad nawet nie wie, co oznacza benchmarking, więc mógł się obrazić.
IAdapter
Właściwa odpowiedź na pytanie (IMO) brzmi: „To w dużej mierze zależy od kompilatora i reszty programu. Napisałbym test porównawczy i przetestował go w 5 minut”
Samuel Rivas
7

Powinny być dokładnie takie same pod względem szybkości.

Zauważ jednak, że niektórzy ludzie umieszczają stałą po lewej stronie w porównaniach równości (tak zwane „warunkowe Yoda”), aby uniknąć wszystkich błędów, które mogą się pojawić, jeśli napiszesz =(operator przypisania) zamiast ==(operator porównania równości); ponieważ przypisanie do literału wyzwala błąd kompilacji, można uniknąć tego rodzaju błędów.

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
{

}

Z drugiej strony, większość ludzi uważa, że ​​„warunkowe Yoda” wyglądają dziwnie i denerwują, zwłaszcza, że ​​klasy błędów, którym zapobiegają, można wykryć również przy użyciu odpowiednich ostrzeżeń kompilatora.

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

}
Matteo Italia
źródło
Dzięki za powtórzenie. Należy jednak pamiętać, że PHP na przykład nie będzie ostrzegać w przypadku przypisań w warunkach warunkowych.
Linus Kleen,
5

Jak powiedzieli inni, nie ma różnicy.

0podlega ocenie. flagpodlega ocenie. Ten proces zajmuje tyle samo czasu, bez względu na to, po której stronie się znajdują.

Prawidłowa odpowiedź brzmiałaby: obie mają taką samą prędkość.

Nawet wyrażenia if(flag==0)i if(0==flag)mają taką samą liczbę znaków! Jeśli jeden z nich if(flag== 0)zostałby zapisany jako , kompilator miałby jedną dodatkową przestrzeń do przeanalizowania, więc miałbyś uzasadniony powód, aby wskazać czas kompilacji.

Ale ponieważ czegoś takiego nie ma, nie ma absolutnie żadnego powodu, dla którego jeden miałby być szybszy od drugiego. Jeśli istnieje powód, kompilator robi bardzo, bardzo dziwne rzeczy z wygenerowanym kodem ...

darioo
źródło
5

Który z nich jest szybki, zależy od używanej wersji ==. Oto fragment, który wykorzystuje 2 możliwe implementacje == i w zależności od tego, czy zdecydujesz się wywołać x == 0, czy 0 == x zostanie wybrana jedna z 2.

Jeśli używasz tylko POD, to naprawdę nie powinno mieć znaczenia, jeśli chodzi o szybkość.

#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);
}
Fanatyk 23
źródło
5

Cóż, całkowicie zgadzam się ze wszystkim, co zostało powiedziane w komentarzach do PO, na potrzeby ćwiczenia:

Jeśli kompilator nie jest wystarczająco sprytny (rzeczywiście nie powinieneś go używać) lub optymalizacja jest wyłączona, x == 0może skompilować się do natywnej jump if zeroinstrukcji asemblera , podczas gdy 0 == xmoże być bardziej ogólnym (i kosztownym) porównaniem wartości liczbowych.

Mimo wszystko nie chciałbym pracować dla szefa, który myśli w ten sposób ...

davka
źródło
4

Z pewnością nie ma różnicy w szybkości wykonywania. Warunek należy ocenić w obu przypadkach w ten sam sposób.

Sachin Shanbhag
źródło
3

Myślę, że najlepszą odpowiedzią jest „w jakim języku jest ten przykład”?

Pytanie nie określało języka i zostało oznaczone jako „C” i „C ++”. Dokładna odpowiedź wymaga więcej informacji.

To kiepskie pytanie programistyczne, ale może być dobre w dziale „dajmy rozmówcy wystarczająco dużo liny, żeby się powiesić lub zbudować huśtawkę na drzewo”. Problem z tego rodzaju pytaniami polega na tym, że zwykle są one zapisywane i przekazywane od osoby przeprowadzającej rozmowę do osoby prowadzącej rozmowę, aż trafiają do osób, które nie rozumieją ich ze wszystkich stron.

Marsh Ray
źródło
3

Zbuduj dwa proste programy, korzystając z sugerowanych sposobów.

Zbierz kody. Spójrz na zgromadzenie i możesz ocenić, ale wątpię, czy jest jakaś różnica!

Wywiady są coraz niższe niż kiedykolwiek.

Błąd składni
źródło
2

Tak na marginesie (myślę, że każdy przyzwoity kompilator sprawi, że to pytanie będzie dyskusyjne, ponieważ zoptymalizuje je) użycie 0 == flag over flag == 0 zapobiega literówce, w której zapomnisz jednego z = (tj. Jeśli przypadkowo wpiszesz flag = 0 skompiluje się, ale 0 = flaga nie), co moim zdaniem jest błędem, który wszyscy popełnili w takim czy innym momencie ...

Kindread
źródło
0

Jeśli w ogóle była różnica, co powstrzymuje kompilator przed wyborem szybszego? Więc logicznie nie może być żadnej różnicy. Prawdopodobnie tego oczekuje ankieter. Właściwie to genialne pytanie.

balki
źródło