Qual deles irá executar mais rápido, se (sinalizador == 0) ou se (0 == sinalizador)?


111

Pergunta da entrevista: Qual deles vai executar mais rápido if (flag==0)ou if (0==flag)? Por quê?


330
Indicado para a pergunta mais estúpida de todas as entrevistas. E há uma competição acirrada.
Konrad Rudolph

119
Você: Cite uma situação em que a diferença entre esses dois possa valer a pena. Entrevistador: Ok, você está contratado.
Chris Lutz

37
A única diferença entre os dois é que, com a convenção posterior, você está seguro contra insetos if(flag = 0)ao preço de um pouco de legibilidade.
Amarghosh

22
@Amarghosh: Ao custo de tornar seu código difícil de ler e não intuitivo. Use o primeiro ao ativar os avisos do compilador, ganha-ganha.
GManNickG

129
Uma vez, um redator do compilador recebeu isso em sua entrevista. Ele sussurrou em resposta, "qual você quer que seja mais rápido?".

Respostas:


236

Ainda não vi nenhuma resposta correta (e já existem algumas) ressalva: Nawaz apontou a armadilha definida pelo usuário . E eu me arrependo de ter votado apressadamente na "pergunta mais estúpida" porque parece que muitos não acertaram e isso dá espaço para uma boa discussão sobre a otimização do compilador :)

A resposta é:

Qual é flago tipo de?

No caso em que flagrealmente é um tipo definido pelo usuário. Então, depende de qual sobrecarga operator==está selecionada. Claro que pode parecer estúpido que eles não sejam simétricos, mas certamente é permitido, e eu já vi outros abusos.

Se flagfor embutido, ambos deverão ter a mesma velocidade.

A partir do artigo da Wikipédia em x86, eu apostaria para uma Jxxinstrução para a ifdeclaração: talvez um JNZ(Salte Se não for Zero) ou algum equivalente.

Eu duvido que o compilador perca uma otimização tão óbvia, mesmo com otimizações desativadas. Este é o tipo de coisa para a qual o Peephole Optimization foi projetado.

EDIT: Sprang up novamente, então vamos adicionar um pouco de montagem (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
}

Mesmo que a pessoa não saiba ler o RI, acho que é autoexplicativo.


4
@Matthieu: você disse que ainda não vi nenhuma resposta correta ... mas a minha está correta, eu acho: P
Nawaz

7
Boa! sua resposta possível transforma "a pergunta mais estúpida" em "os truques / mesquinhos". "vamos cavar um buraco para o candidato e ver se ele cai nele ..." :) Acho que todos nós assumimos automaticamente que flagdeve ser inteiro ou booleano. OTOH, ter uma variável chamada flagde um tipo definido pelo usuário é bastante errado em si mesmo, IMHO
davka

@Nawaz: Posso ter pulado o último parágrafo de sua resposta: p
Matthieu M.

1
@Nawaz: Eu realmente não corro, geralmente leio perguntas muito depois de terem sido respondidas e as pessoas tendem a ler apenas as primeiras respostas mais votadas :) Mas na verdade estou lendo coisas sobre otimizações de compiladores, e isso me pareceu um Caso típico de otimização trivial, pensei em apontá-lo para os leitores que realmente se importam ... Estou muito surpreso de ter recebido tantos votos positivos. Agora é minha resposta mais votada, embora certamente não seja aquela em que eu coloquei mais esforço: / De qualquer forma, editei minha resposta e corrigi minha declaração :)
Matthieu M.

2
@mr_eclair: um tipo embutido é um tipo que é (como o nome indica) embutido na linguagem. Ou seja, está disponível mesmo sem uma única #includediretiva. Para simplificar, que normalmente equivale a int, char, boole similares. Todos os outros tipos são disse a ser definida pelo usuário, que é que eles existem, porque eles são o resultado de algum usuário declarando-os: typedef, enum, struct, class. Por exemplo, std::stringé definido pelo usuário, embora você certamente não o tenha definido :)
Matthieu M.

56

Mesmo código para amd64 com 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 para ir mais longe para provar que a otimização do compilador é a mesma.
k rey

56

Não haverá diferença em suas versões.

Estou assumindo que o typesinalizador de não é um tipo definido pelo usuário, mas sim algum tipo integrado. Enum é exceção! . Você pode tratar enum como se estivesse embutido. Na verdade, seus valores são de um tipo embutido!

No caso, se for do tipo definido pelo usuário (exceto enum), então a resposta depende inteiramente de como você sobrecarregou o operador ==. Observe que você deve sobrecarregar ==definindo duas funções, uma para cada uma de suas versões!


8
esta poderia ser a única razão possível para fazer esta pergunta, IMHO
davka

15
Eu ficaria extremamente surpreso se os compiladores modernos perdessem essa otimização óbvia.
Pedro d'Aquino

3
No meu conhecimento ! não é uma operação bit a bit
Xavier Combelle

8
@Nawaz: não votou negativamente, mas sua resposta está factualmente errada e é horrível que ainda tenha tantos votos positivos. Para o registro, comparar um número inteiro com 0 é uma única instrução de montagem , completamente parecida com a negação. Na verdade, se o compilador for um pouco estúpido, isso poderia até ser mais rápido do que a negação (embora não seja provável).
Konrad Rudolph

6
@Nawaz: ainda é errado dizer que pode, será ou geralmente será mais rápido. Se houver diferença, então a versão "compare com zero" será mais rápida, já que a negação um se traduz em duas operações: "negar operando; verifique se o resultado é diferente de zero". Na prática, é claro, o compilador o otimiza para produzir o mesmo código que a versão simples "compare com zero", mas a otimização está sendo aplicada à versão de negação, para fazê-la alcançá-la, e não o contrário. Konrad está certo.
jalf

27

Não há absolutamente nenhuma diferença.

Você pode ganhar pontos ao responder a essa pergunta da entrevista, referindo-se à eliminação de erros de atribuição / comparação, embora:

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

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

Embora seja verdade, que, por exemplo, um compilador C avisa no caso do anterior ( flag = 0), não existem tais avisos em PHP, Perl ou Javascript ou <insert language here>.


@Matthieu Huh. Devo ter perdido o post sobre meta descrevendo o estilo de órtese "adequado".
Linus Kleen

7
Não votei nada, mas vale a pena: por que é tão importante que as pessoas se expliquem sempre que votam? Os votos são anônimos por design. Oponho-me inteiramente à ideia de que os downvoters devam sempre comentar, porque pessoalmente não quero ser considerado o downvoter apenas porque deixei um comentário apontando um problema. Talvez o downvoter tenha pensado que a maioria da resposta era irrelevante para a questão da velocidade? Talvez ele tenha pensado que encorajava um estilo de codificação que ele não aprovava? Talvez ele fosse um idiota e quisesse que sua própria resposta tivesse a pontuação mais alta?
David Hedlund

3
As pessoas devem ser livres para votar como quiserem, independentemente do motivo. Em termos de reputação, isso é quase sempre uma coisa boa, pois muitas vezes provoca outras pessoas a votar positivamente, para contrariar o downvote imerecido, quando na verdade, um único upvote cancelaria cinco downvotes imerecidos.
David Hedlund

26
@David: Os Downvoters devem se explicar porque este site não é sobre votos secretos de popularidade, votação anônima ou algo parecido. Este site é sobre aprendizado. Se alguém disser que uma resposta está incorreta, o downvoter está sendo egoísta com seu conhecimento se não explicar o porquê. Eles estão dispostos a receber todo o crédito quando estão certos, mas não estão dispostos a compartilhar conhecimento quando os outros estão errados.
John Dibling

1
Só para tirar a questão do estilo estimulante do caminho, eu realmente acho que Matthieu considerou isso uma piada. Eu ficaria surpreso em ver que alguém dá seus votos dependendo dessas questões. Dito isso, nem todo mundo usa os votos exatamente da mesma maneira. Eu pude ver a razão para downvoting porque a postagem parece defender um estilo de codificação que o eleitor pode desaprovar (observe a diferença entre defender um estilo de codificação - "se você escrever seu código assim, obterá um erro do compilador quando fizer este erro de digitação "- e simplesmente usando um estilo de codificação, como colchetes) Nesse ...
David Hedlund

16

Não haverá absolutamente nenhuma diferença em termos de velocidade. Por que deveria haver?


7
se o compilador foi completamente retardado. Essa é a única razão.
JeremyP

@JeremyP: Não consigo imaginar diferença, mesmo que o compilador fosse retardado. O redator do compilador teria que fazer isso de propósito , pelo que posso dizer.
Jon

2
Supondo que o processador tenha uma instrução "teste se 0", x == 0pode ser usado, mas 0 == xpode usar uma comparação normal. Eu disse que teria que ser retardado.
JeremyP

8
Se o sinalizador for um tipo definido pelo usuário com uma sobrecarga assimétrica do operador == ()
OrangeDog

Porque nós pode ter virtual operator==(int)em um tipo definido pelo usuário?
Lorro

12

Bem, há uma diferença quando o sinalizador é um tipo definido pelo usuário

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

No primeiro caso (0 == s), o operador de conversão é chamado e, em seguida, o resultado retornado é comparado a 0. No segundo caso, o operador == é chamado.


3
+1 por mencionar que um operador de conversão pode ser tão relevante quanto um operador ==.
Tony Delroy,

11

Em caso de dúvida, faça um benchmark e aprenda a verdade.


2
o que há de errado com o benchmarking? às vezes, a prática diz mais do que a teoria
Elzo Valugi

1
Essa é a resposta que eu procurava quando comecei a ler este tópico. Parece que a teoria é mais atraente do que a prática, olhando as respostas e votos positivos :)
Samuel Rivas

como ele poderia avaliar na entrevista? Além disso, acho que o entrevistador nem sabe o que significa benchmarking, então ele pode ter ficado ofendido.
IAdapter

A resposta certa para a pergunta (IMO) é "Isso depende muito do compilador e do resto do programa. Eu escreveria um benchmark e testaria em 5 minutos"
Samuel Rivas

7

Eles devem ser exatamente os mesmos em termos de velocidade.

Observe, entretanto, que algumas pessoas costumam colocar a constante à esquerda em comparações de igualdade (os chamados "condicionais de Yoda") para evitar todos os erros que podem surgir se você escrever =(operador de atribuição) em vez de ==(operador de comparação de igualdade); uma vez que a atribuição a um literal aciona um erro de compilação, esse tipo de erro é evitado.

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
{

}

Por outro lado, a maioria das pessoas acha as "condicionais do Yoda" estranhas e irritantes, especialmente porque a classe de erros que elas evitam também pode ser detectada usando avisos do compilador adequados.

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

}

Obrigado por ecoar. Note, entretanto, que o PHP, por exemplo, não avisará no caso de atribuições em condicionais.
Linus Kleen

5

Como outros já disseram, não há diferença.

0tem que ser avaliado. flagtem que ser avaliado. Esse processo leva o mesmo tempo, não importa de que lado estão colocados.

A resposta certa seria: ambos têm a mesma velocidade.

Mesmo as expressões if(flag==0)e if(0==flag)têm a mesma quantidade de caracteres! Se um deles foi escrito como if(flag== 0), o compilador teria um espaço extra para analisar, então você teria um motivo legítimo para apontar o tempo de compilação.

Mas, como não existe tal coisa, não há absolutamente nenhuma razão para que um seja mais rápido do que o outro. Se houver um motivo, o compilador está fazendo coisas muito, muito estranhas no código gerado ...


5

Qual é o mais rápido depende de qual versão de == você está usando. Aqui está um snippet que usa 2 implementações possíveis de == e, dependendo se você escolhe chamar x == 0 ou 0 == x, um dos 2 é selecionado.

Se você estiver usando apenas um POD, isso realmente não deve importar quando se trata de velocidade.

#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

Bem, estou concordando totalmente com tudo o que foi dito nos comentários ao OP, para fins de exercício:

Se o compilador não for inteligente o suficiente (na verdade, você não deve usá-lo) ou a otimização estiver desabilitada, x == 0pode compilar para uma jump if zeroinstrução de montagem nativa , enquanto 0 == xpoderia ser uma comparação mais genérica (e cara) de valores numéricos.

Ainda assim, não gostaria de trabalhar para um chefe que pensa nestes termos ...


4

Certamente nenhuma diferença em termos de velocidade de execução. A condição deve ser avaliada em ambos os casos da mesma maneira.


3

Acho que a melhor resposta é "em que idioma está esse exemplo"?

A pergunta não especifica o idioma e está marcada com 'C' e 'C ++'. Uma resposta precisa precisa de mais informações.

É uma péssima pergunta de programação, mas poderia ser uma boa no tortuoso departamento "vamos dar ao entrevistado corda suficiente para se enforcar ou construir um balanço de árvore". O problema com esse tipo de pergunta é que geralmente são escritas e passadas de entrevistador para entrevistador, até chegar a pessoas que não as entendem de todos os ângulos.


3

Construa dois programas simples usando as maneiras sugeridas.

Reúna os códigos. Olhe a assembléia e você pode julgar, mas duvido que haja diferença!

As entrevistas estão ficando mais baixas do que nunca.


2

Apenas como um aparte (eu realmente acho que qualquer compilador decente tornará esta questão discutível, uma vez que irá otimizá-la) usando 0 == sinalizador sobre sinalizador == 0 evita o erro de digitação onde você esquece um dos = (ou seja, se você digitar acidentalmente flag = 0 ele irá compilar, mas 0 = flag não), o que eu acho que é um erro que todos cometeram em um ponto ou outro ...


0

Se houve alguma diferença, o que impede o compilador de escolher o mais rápido uma vez? Então, logicamente, não pode haver nenhuma diferença. Provavelmente é isso que o entrevistador espera. Na verdade, é uma pergunta brilhante.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.