O que é mais rápido: while (1) ou while (2)?


587

Esta foi uma pergunta da entrevista feita por um gerente sênior.

O que é mais rápido?

while(1) {
    // Some code
}

ou

while(2) {
    //Some code
}

Eu disse que ambos têm a mesma velocidade de execução, pois a expressão dentro whiledeve finalmente avaliar para trueou false. Nesse caso, ambos avaliam truee não há instruções condicionais extras dentro da whilecondição. Então, ambos terão a mesma velocidade de execução e eu prefiro enquanto (1).

Mas o entrevistador disse com confiança: "Verifique seu básico. while(1)É mais rápido que while(2)". (Ele não estava testando minha confiança)

Isso é verdade?

Veja também: "for (;;)" é mais rápido que "while (TRUE)"? Se não, por que as pessoas o usam?


202
Um compilador meio decente otimizará ambas as formas para nada.

64
Na compilação otimizada a cada momento (n), n! = 0 ou for (;;) será traduzido para loop infinito de Assembly com rótulo no início e goto no final. Exatamente o mesmo código, o mesmo desempenho.
Alex F

60
Não é de surpreender que uma otimização de estoque traga 0x100000f90: jmp 0x100000f90(endereço varia, obviamente) para ambos os trechos. O entrevistador provavelmente fez hedge em um teste de registro versus um simples salto sinalizado. Tanto a questão quanto sua suposição são esfarrapadas.
WhozCraig

50
Essa pergunta do entrevistador se enquadra nos mesmos auspícios de dilbert.com/strips/comic/1995-11-17 - você encontrará alguém que realmente acredita no que está dizendo, independentemente do quociente de estupidez em sua declaração. Simplesmente escolha uma das seguintes
opções

3
@ Mike W: pode-se perguntar o que um compilador deve fazer: traduzir para uma instrução Halt ou considerar que o loop sai após um tempo infinito e otimizar o atraso infinito?
Yves Daoust

Respostas:


681

Ambos os loops são infinitos, mas podemos ver qual deles leva mais instruções / recursos por iteração.

Usando o gcc, compilei os dois programas a seguir para montagem em vários níveis de otimização:

int main(void) {
    while(1) {}
    return 0;
}


int main(void) {
    while(2) {}
    return 0;
}

Mesmo sem otimizações ( -O0), o assembly gerado era idêntico para os dois programas . Portanto, não há diferença de velocidade entre os dois loops.

Para referência, aqui está o assembly gerado (usando gcc main.c -S -masm=intelcom um sinalizador de otimização):

Com -O0:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    push    rbp
    .seh_pushreg    rbp
    mov rbp, rsp
    .seh_setframe   rbp, 0
    sub rsp, 32
    .seh_stackalloc 32
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Com -O1:

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .text
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Com -O2e -O3(mesma saída):

    .file   "main.c"
    .intel_syntax noprefix
    .def    __main; .scl    2;  .type   32; .endef
    .section    .text.startup,"x"
    .p2align 4,,15
    .globl  main
    .def    main;   .scl    2;  .type   32; .endef
    .seh_proc   main
main:
    sub rsp, 40
    .seh_stackalloc 40
    .seh_endprologue
    call    __main
.L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

De fato, a montagem gerada para o loop é idêntica para todos os níveis de otimização:

 .L2:
    jmp .L2
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Os bits importantes são:

.L2:
    jmp .L2

Não consigo ler a montagem muito bem, mas esse é obviamente um loop incondicional. A jmpinstrução redefine incondicionalmente o programa de volta ao .L2rótulo, sem mesmo comparar um valor com true, e, é claro, imediatamente o faz novamente até que o programa seja encerrado de alguma forma. Isso corresponde diretamente ao código C / C ++:

L2:
    goto L2;

Editar:

Curiosamente, mesmo sem otimizações , todos os seguintes loops produziram exatamente a mesma saída (incondicional jmp) na montagem:

while(42) {}

while(1==1) {}

while(2==2) {}

while(4<7) {}

while(3==3 && 4==4) {}

while(8-9 < 0) {}

while(4.3 * 3e4 >= 2 << 6) {}

while(-0.1 + 02) {}

E até para minha surpresa:

#include<math.h>

while(sqrt(7)) {}

while(hypot(3,4)) {}

As coisas ficam um pouco mais interessantes com as funções definidas pelo usuário:

int x(void) {
    return 1;
}

while(x()) {}


#include<math.h>

double x(void) {
    return sqrt(7);
}

while(x()) {}

Na -O0, esses dois exemplos realmente chamam xe executam uma comparação para cada iteração.

Primeiro exemplo (retornando 1):

.L4:
    call    x
    testl   %eax, %eax
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

Segundo exemplo (retornando sqrt(7)):

.L4:
    call    x
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jp  .L4
    xorpd   %xmm1, %xmm1
    ucomisd %xmm1, %xmm0
    jne .L4
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .ident  "GCC: (tdm64-2) 4.8.1"

No entanto, -O1acima e acima, ambos produzem o mesmo conjunto que os exemplos anteriores (um jmpretorno incondicional ao rótulo anterior).

TL; DR

Sob o GCC, os diferentes loops são compilados para montagem idêntica. O compilador avalia os valores constantes e não se incomoda em realizar nenhuma comparação real.

A moral da história é:

  • Existe uma camada de tradução entre o código fonte C ++ e as instruções da CPU, e essa camada tem implicações importantes para o desempenho.
  • Portanto, o desempenho não pode ser avaliado olhando apenas o código-fonte.
  • O compilador deve ser inteligente o suficiente para otimizar esses casos triviais. Os programadores não devem perder tempo pensando neles na grande maioria dos casos.

196
Talvez o entrevistador não estava usando gcc
MM

106
@Matt McNabb Esse é um bom argumento, mas se o entrevistador estava confiando em otimizações específicas do compilador, ele precisa ser muito explícito sobre isso em sua pergunta e precisa aceitar a resposta "não há diferença" como correta. alguns (a maioria?) compiladores.
22614 Jonathan

109
Para remover qualquer dúvida, testei isso no clang 3.4.2, e os dois loops produzem o mesmo conjunto em todos os -Oníveis.
Martin Tournoij

16
Não acho isso surpreendente, pois tudo o que você colocou na seção condicional dos loops são constantes em tempo de compilação. Como tal, eu suspeitaria que um compilador verificaria que os loops serão sempre verdadeiros ou falsos e, respectivamente, simplesmente de jmpvolta ao início ou remover completamente o loop.
precisa saber é o seguinte

10
@hippietrail Não vejo como o conteúdo (ou a falta dele) do loop poderia afetar essas otimizações (exceto a possibilidade de qualquer breakdeclaração), mas eu apenas testei mesmo assim e não, mesmo com o código dentro do loop, o salto é absoluto e incondicional para ambos while(1)e while(2). Sinta-se à vontade para testar os outros se estiver realmente preocupado.
ApproachingDarknessFish

282

Sim, while(1)é muito mais rápido do que while(2), para um ser humano para ler! Se eu while(1)vir uma base de código desconhecida, sei imediatamente o que o autor pretendia, e meus olhos podem continuar na próxima linha.

Se eu vir while(2), provavelmente vou parar nas minhas trilhas e tentar descobrir por que o autor não escreveu while(1). O dedo do autor escorregou no teclado? Os mantenedores dessa base de código usam while(n)como um mecanismo de comentário obscuro para fazer com que os loops pareçam diferentes? É uma solução grosseira para um aviso falso em alguma ferramenta de análise estática quebrada? Ou isso é uma pista de que estou lendo o código gerado? É um bug resultante de uma busca e substituição inadequadas de tudo ou de uma má fusão ou de um raio cósmico? Talvez essa linha de código deva fazer algo dramaticamente diferente. Talvez fosse para ler while(w)ou while(x2). É melhor encontrar o autor no histórico do arquivo e enviar a ele um e-mail "WTF" ... e agora quebrei meu contexto mental.while(2)while(1) levaria uma fração de segundo!

Estou exagerando, mas só um pouco. A legibilidade do código é realmente importante. E isso vale a pena mencionar em uma entrevista!


Meus pensamentos exatamente, e precisamente por que enquanto (1) é mais rápido, lol Embora eu ache que era simplesmente o caso antigo de um gerente tentando se convencer de que sabe sobre codificação quando não o faz, todos já vimos isso, todo mundo quer ser um jedi.
ekerner

8
Absolutamente, isso não é exagero. Definitivamente o svn-annotate ou git blame(ou o que seja) será usado aqui e, em geral, leva alguns minutos para carregar um histórico de culpa do arquivo. Em seguida, basta finalmente decidir "ah eu entendo, o autor escreveu esta linha quando fora fresco de ensino médio", perdeu apenas 10 minutos ...
v.oddou

11
Votado porque estou cansado de convenções. O desenvolvedor tem essa oportunidade insignificante de escrever um número que ele gosta, então alguém entediante chega e reclama por que não é 1.
Utkan Gezer

1
Votado devido à retórica. Ler enquanto (1) é exatamente a mesma velocidade que enquanto (2).
hdante

4
Passei exatamente pelo mesmo processo mental descrito nesta resposta há um minuto, quando vi while(2)o código (considerando "Os mantenedores dessa base de código usam while (n) como um mecanismo de comentário obscuro para fazer com que os loops pareçam diferentes?" ) Então não, você não está exagerando!
Hisham HM

151

As respostas existentes que mostram o código gerado por um compilador específico para um destino específico com um conjunto específico de opções não respondem totalmente à pergunta - a menos que a pergunta tenha sido feita nesse contexto específico ("O que é mais rápido usando o gcc 4.7.2 para x86_64" com opções padrão? ", por exemplo).

No que diz respeito à definição de linguagem, na máquina abstrata while (1) avalia a constante inteira 1e while (2)avalia a constante inteira 2; nos dois casos, o resultado é comparado para igualdade a zero. O padrão da linguagem não diz absolutamente nada sobre o desempenho relativo das duas construções.

Eu posso imaginar que um compilador extremamente ingênuo possa gerar código de máquina diferente para os dois formulários, pelo menos quando compilado sem solicitar otimização.

Por outro lado, os compiladores C absolutamente precisam avaliar algumas expressões constantes em tempo de compilação, quando elas aparecem em contextos que exigem uma expressão constante. Por exemplo, isto:

int n = 4;
switch (n) {
    case 2+2: break;
    case 4:   break;
}

requer um diagnóstico; um compilador lento não tem a opção de adiar a avaliação 2+2até o tempo de execução. Como um compilador precisa ter a capacidade de avaliar expressões constantes em tempo de compilação, não há uma boa razão para não tirar proveito desse recurso, mesmo quando não é necessário.

O padrão C ( N1570 6.8.5p4) diz que

Uma instrução de iteração faz com que uma instrução chamada corpo do loop seja executada repetidamente até que a expressão de controle seja comparada a 0.

Portanto, as expressões constantes relevantes são 1 == 0e 2 == 0, ambas avaliadas para o intvalor 0. (Essas comparações estão implícitas na semântica do whileloop; elas não existem como expressões C. reais).

Um compilador perversamente ingênuo pode gerar código diferente para as duas construções. Por exemplo, no primeiro, poderia gerar um loop infinito incondicional (tratado 1como um caso especial) e, no segundo, poderia gerar uma comparação explícita em tempo de execução equivalente a 2 != 0. Mas nunca encontrei um compilador C que realmente se comportasse dessa maneira e duvido seriamente que esse compilador exista.

A maioria dos compiladores (estou tentada a dizer que todos os compiladores de qualidade de produção) têm opções para solicitar otimizações adicionais. Sob essa opção, é ainda menos provável que qualquer compilador gere código diferente para os dois formulários.

Se o seu compilador gerar código diferente para as duas construções, verifique primeiro se as diferentes seqüências de código realmente têm desempenho diferente. Se o fizerem, tente compilar novamente com uma opção de otimização (se disponível). Se eles ainda diferirem, envie um relatório de bug ao fornecedor do compilador. Não é (necessariamente) um bug no sentido de uma falha em conformidade com o padrão C, mas é quase certamente um problema que deve ser corrigido.

Conclusão: while (1)e while(2) quase certamente tem o mesmo desempenho. Eles têm exatamente a mesma semântica e não há uma boa razão para qualquer compilador não gerar código idêntico.

E embora seja perfeitamente legal para um compilador gerar código mais rápido do while(1)que para while(2), é igualmente legal para um compilador gerar código mais rápido do while(1)que para outra ocorrência while(1)no mesmo programa.

(Há outra pergunta implícita na pergunta: como você lida com um entrevistador que insiste em um ponto técnico incorreto. Essa provavelmente seria uma boa pergunta para o site Workplace ).


8
"Nesse caso, as expressões constantes relevantes (implícitas) são 1! = 0 e 2! = 0, ambas avaliadas com o valor int 1" ... Isso é muito complicado e impreciso. O padrão simplesmente diz que a expressão de controle de whiledeve ser do tipo escalar e o corpo do loop é repetido até que a expressão seja comparada a 0. Ele não diz que há um implícito expr != 0que é avaliado ... que exigiria o resultado de que - 0 ou 1 - por sua vez, é comparado a 0, ad infinitum. Não, a expressão é comparada a 0, mas essa comparação não produz um valor. PS eu votei.
Jim Balter

3
@ JimBalter: Entendo o seu ponto e atualizarei minha resposta para resolvê-lo. O que eu quis dizer, porém, foi que a redação do padrão "... até que a expressão de controle seja comparada a 0" implica em avaliação <expr> == 0; é isso que "compara igual a 0" significa em C. Essa comparação faz parte da semântica do whileloop. Não há implicação, nem no padrão nem na minha resposta, de que o resultado precise ser comparado 0novamente. (Eu deveria ter escrito, em ==vez de !=.) Mas essa parte da minha resposta não estava clara e eu a atualizarei.
21414 Keith Thompson

1
"" compara igual a 0 "significa em C" - mas essa linguagem está no padrão , não em C ... a implementação faz a comparação, não gera uma comparação na linguagem C. Você escreve "ambos os quais são avaliados com o valor int 1" - mas nunca ocorre essa avaliação. Se você olhar para o código gerado para while (2), while (pointer), while (9.67), pelo compilador mais ingênuo, unoptimized, você não vai ver qualquer código gerado que os rendimentos 0ou 1. "Eu deveria ter escrito == ao invés de! =" - não, isso não faria sentido.
Jim Balter 21/07

2
@ JimBalter: Hmmm. Não quero sugerir que "compara igual a 0" implica a existência de uma ... == 0expressão C. O que quero dizer é que tanto o "compara igual a 0" exigido pela descrição padrão dos whileloops quanto uma x == 0expressão explícita implicam logicamente a mesma operação. E acho que um compilador C dolorosamente ingênuo pode gerar código que gera um intvalor de 0ou 1para qualquer whileloop - embora eu não acredite que qualquer compilador real seja tão ingênuo.
21414 Keith Thompson

12
Joe

141

Espere um minuto. O entrevistador, ele se parecia com esse cara?

insira a descrição da imagem aqui

Já é suficientemente ruim que o próprio entrevistador tenha falhado nesta entrevista, e se outros programadores nesta empresa "passaram" neste teste?

Não. Avaliar as declarações 1 == 0e 2 == 0 deve ser igualmente rápido. Nós poderia imaginar implementações do compilador pobres onde se pode ser mais rápido do que o outro. Mas não há uma boa razão para que um seja mais rápido que o outro.

Mesmo se houver alguma circunstância obscura em que a alegação seria verdadeira, os programadores não devem ser avaliados com base no conhecimento de trivialidades obscuras (e, neste caso, assustadoras). Não se preocupe com esta entrevista, a melhor jogada aqui é ir embora.

Disclaimer: Este não é um desenho animado original de Dilbert. Isso é apenas um mashup .


6
Isso seria engraçado se também contivesse uma resposta para a pergunta.
Cody Gray

não, mas realmente, todos podemos imaginar com bastante facilidade que todos os compiladores escritos por empresas sérias produzirão código razoável. vamos pegar o "caso não otimizado" / O0, talvez ele acabe como o anatolyg postou. Então é uma questão de CPU, o cmpoperando será executado em menos ciclos comparando 1 a 0 que 2 a 0? quantos ciclos são necessários para executar o cmp em geral? é variável de acordo com padrões de bits? eles são padrões de bits mais "complexos" que diminuem a velocidade cmp? Eu não conheço pessoalmente. você pode imaginar uma implementação super idiota verificando pouco a pouco do ranking 0 ao n (por exemplo, n = 31).
precisa saber é

5
Esse é o meu ponto também: o cmpoperando deve ser igualmente rápido para 1 e 200. Provavelmente, poderíamos imaginar implementações idiotas onde esse não é o caso. Mas podemos imaginar uma implementação não-idiota onde while(1)é mais rápida que while(200)? Da mesma forma, se em alguma era pré-histórica, a única implementação disponível era idiota assim, devemos discutir isso hoje? Acho que não, é uma conversa de chefe de cabelos pontudos e uma verdadeira jóia!
Janos

@ v.ouddou "o operando cmp será executado em menos ciclos comparando 1 a 0 que 2 a 0" - Não. Você deve aprender o que é um ciclo. "Eu não conheço pessoalmente. Você poderia imaginar uma implementação super idiota verificando pouco a pouco do ranking 0 ao n" - ou o contrário, ainda fazendo do entrevistador um idiota sem noção. E por que se preocupar em verificar pouco a pouco? A implementação pode ser um homem em uma caixa que decide fazer uma pausa para o almoço no meio da avaliação do seu programa.
Jim Balter

79

Sua explicação está correta. Essa parece ser uma pergunta que testa sua autoconfiança, além do conhecimento técnico.

A propósito, se você respondeu

Ambas as partes do código são igualmente rápidas, porque levam um tempo infinito para serem concluídas.

o entrevistador diria

Mas while (1)pode fazer mais iterações por segundo; você pode explicar o porquê? (isso é um absurdo; testar sua confiança novamente)

Então, ao responder como você fez, você economizou algum tempo que, de outra forma, desperdiçaria ao discutir essa pergunta ruim.


Aqui está um exemplo de código gerado pelo compilador no meu sistema (MS Visual Studio 2012), com as otimizações desativadas:

yyy:
    xor eax, eax
    cmp eax, 1     (or 2, depending on your code)
    je xxx
    jmp yyy
xxx:
    ...

Com as otimizações ativadas:

xxx:
    jmp xxx

Portanto, o código gerado é exatamente o mesmo, pelo menos com um compilador de otimização.


28
Esse código é realmente o que o compilador gera no meu sistema. Eu não inventei.
Anatolyg

10
icepack "O operando por enquanto é do tipo booleano" - absurdo total. Você é o entrevistador? Sugiro que você se familiarize com a linguagem C e seu padrão antes de fazer essas afirmações.
Jim Balter

29
"Eu não inventei." - Por favor, não preste atenção no saco de gelo, que está falando bobagem. C não tem tipo booleano (ele possui _Bool em stdbool.h, mas não é o mesmo, e a semântica de whilemuito tempo o precedeu) e o operando de whilenão é booleano ou _Bool ou qualquer outro tipo específico. O operando de whilepode ser qualquer expressão ... o while quebra em 0 e continua em não-0.
Jim Balter

36
"Ambas as partes do código são igualmente rápidas, porque ambas levam um tempo infinito para serem concluídas" me fizeram pensar em algo interessante. As únicas maneiras de terminar um loop infinito seriam um pouco invertidas por uma partícula carregada ou por falha do hardware: alterando a instrução de while (00000001) {}para while (00000000) {}. Quanto mais bits verdadeiros você tiver, menor a chance do valor virar falso. Infelizmente, 2 também tem apenas um bit verdadeiro. 3, no entanto, duraria significativamente mais tempo. Isso também se aplica apenas a um compilador que nem sempre otimiza isso (VC ++).
9788 Jonathan Bachman

8
@ mr5 não. Para que um pouco de flip realmente resulte em algo assim, você está falando sobre o tempo de execução em dezenas de milhares de anos. Apenas um experimento mental. Se você é oriundo de uma corrida imortal, pode usar -1 para evitar que a troca de bits afete seu programa.
9788 Jonathan

60

A explicação mais provável para a pergunta é que o entrevistador pensa que o processador verifica os bits individuais dos números, um por um, até atingir um valor diferente de zero:

1 = 00000001
2 = 00000010

Se o "é zero?" Se o algoritmo começar do lado direito do número e precisar verificar cada bit até atingir um bit diferente de zero, o while(1) { }loop precisará verificar o dobro de bits por iteração que o while(2) { }loop.

Isso requer um modelo mental muito errado de como os computadores funcionam, mas ele tem sua própria lógica interna. Uma maneira de verificar seria perguntar se while(-1) { }ou while(3) { }seria igualmente rápido ou se while(32) { }seria ainda mais lento .


39
Presumi que a compreensão errada do entrevistador seria mais como "2 é um int que precisa ser convertido em booleano para ser usado em uma expressão condicional, enquanto 1 já é booleano".
Russell Borogove

7
E se o algoritmo de comparação começar da esquerda, é o contrário.
Pa Elo Ebermann

1
+1, foi exatamente o que pensei. você poderia perfeitamente imaginar alguém acreditando que, fundamentalmente, o cmpalgoritmo de uma CPU é uma verificação de bits linear com saída de loop antecipada na primeira diferença.
v.oddou

1
@PeterCordes "Eu nunca ouvi ninguém (além de você) argumentar que a -O0saída de gcc ou clang não é literal o suficiente." Eu nunca disse isso e não tinha em mente o gcc ou o clang. Você deve estar me interpretando mal. Não sou da sua opinião que o que a MSVC produz é hilário.
precisa saber é o seguinte

1
@ PeterCordes Eu estava errado, no MSVC um ponto de interrupção no while(1)não quebra antes do loop, mas na expressão. Mas ainda entra em colapso dentro do loop ao otimizar a expressão. Pegue esse código com muitas pausas. No modo de depuração não otimizado, você obtém isso . Ao otimizar, muitos pontos de interrupção se encaixam ou se espalham para a próxima função (o IDE mostra os pontos de interrupção do resultado durante a depuração) - desmontagem correspondente .
precisa saber é o seguinte

32

É claro que não conheço as reais intenções desse gerente, mas proponho uma visão completamente diferente: ao contratar um novo membro para uma equipe, é útil saber como ele reage a situações de conflito.

Eles o levaram a um conflito. Se isso for verdade, eles são espertos e a pergunta foi boa. Para alguns setores, como o setor bancário, a publicação do seu problema no Stack Overflow pode ser um motivo de rejeição.

Mas é claro que não sei, apenas proponho uma opção.


É realmente excelente, mas enquanto (2) vs enquanto (1) são obviamente retirados dos quadrinhos de dilbert. NÃO PODE ser inventado por alguém em sã consciência (como é que alguém consegue enquanto (2) como uma coisa possível de escrever, afinal?). Se sua hipótese fosse verdadeira, definitivamente você daria um problema tão único que pode pesquisar no Google. Como "é enquanto (0xf00b442) mais lento que enquanto (1)", como o banco consideraria a pergunta do inerviewee de outra forma? você acha que eles são da NSA e têm acesso ao keycore?
v.oddou

25

Eu acho que a pista pode ser encontrada em "solicitado por um gerente sênior". Obviamente, essa pessoa parou de programar quando se tornou gerente e depois levou vários anos para se tornar gerente sênior. Nunca perdeu o interesse em programação, mas nunca escreveu uma linha desde aqueles dias. Portanto, sua referência não é "nenhum compilador decente", como algumas respostas mencionam, mas "o compilador com quem essa pessoa trabalhou 20 a 30 anos atrás".

Naquela época, os programadores passavam uma porcentagem considerável de seu tempo testando vários métodos para tornar seu código mais rápido e mais eficiente, já que o tempo de CPU do 'minicomputador central' era muito valioso. Assim como as pessoas que escrevem compiladores. Suponho que o único compilador que sua empresa disponibilizou naquele momento otimizou com base em 'declarações frequentemente encontradas que podem ser otimizadas' e tomou um atalho ao encontrar um tempo (1) e avaliou tudo mais, incluindo um tempo (2). Ter uma experiência assim poderia explicar sua posição e sua confiança nela.

A melhor abordagem para você ser contratado é provavelmente uma que permita que o gerente sênior se empolgue e faça uma palestra de 2 a 3 minutos sobre "os bons e velhos tempos da programação" antes de VOCÊ o levar suavemente para o próximo assunto da entrevista. (O bom momento é importante aqui - rápido demais e você está interrompendo a história - muito lento e você é rotulado como alguém com foco insuficiente). Diga a ele no final da entrevista que você ficaria muito interessado em aprender mais sobre esse tópico.


19

Você deveria ter perguntado a ele como ele chegou a essa conclusão. Sob qualquer compilador decente, os dois compilam com as mesmas instruções asm. Então, ele deveria ter dito a você o compilador para começar. E mesmo assim, você precisaria conhecer muito bem o compilador e a plataforma para fazer um palpite teórico. E, no final, isso realmente não importa na prática, pois existem outros fatores externos, como fragmentação da memória ou carga do sistema, que influenciarão mais o loop do que esse detalhe.


14
@GKFX Se você deu sua resposta e ela lhe diz que está errado, não há motivo para não pedir que expliquem o motivo. Se Anatolyg está correto e é um teste de sua autoconfiança, você deve explicar por que respondeu da maneira que respondeu e perguntar o mesmo.
P.Turpie

Eu quis dizer como a primeira coisa que você diz a eles. Não pode ser "Por que x é mais rápido?" "Eu não sei; por que x é mais rápido?". Obviamente, tendo respondido corretamente, você pode perguntar.
GKFX

18

Por essa questão, devo acrescentar que lembro-me de Doug Gwyn, do Comitê C, escrevendo que alguns compiladores C anteriores, sem a aprovação do otimizador, gerariam um teste em assembly para o while(1)(comparando com o for(;;)que não o teria).

Eu respondia ao entrevistador dando essa nota histórica e depois dizia que, mesmo que eu ficasse surpreso com qualquer compilador, o compilador poderia ter:

  • sem otimizador passar o compilador gerar um teste para ambos while(1)ewhile(2)
  • com a otimização, o compilador é instruído a otimizar (com um salto incondicional) tudo while(1)porque eles são considerados idiomáticos. Isso deixaria o while(2)teste e, portanto, faria uma diferença de desempenho entre os dois.

É claro que eu acrescentaria ao entrevistador que não considerar while(1)e while(2)o mesmo construto é um sinal de otimização de baixa qualidade, pois esses são construtos equivalentes.


10

Outra opinião sobre essa questão seria ver se você tem coragem de dizer ao seu gerente que ele / ela está errado! E quão suavemente você pode comunicá-lo.

Meu primeiro instinto seria gerar a saída do assembly para mostrar ao gerente que qualquer compilador decente deveria cuidar dele, e se não estiver fazendo isso, você enviará o próximo patch para ele :)


8

Ver tantas pessoas se aprofundando nesse problema mostra exatamente por que isso poderia muito bem ser um teste para ver com que rapidez você deseja otimizar as coisas.

Minha resposta seria; não importa muito, prefiro me concentrar no problema de negócios que estamos resolvendo. Afinal, é por isso que vou ser pago.

Além disso, eu preferiria, while(1) {}porque é mais comum, e outros colegas de equipe não precisariam gastar tempo para descobrir por que alguém aceitaria um número maior que 1.

Agora vá escrever algum código. ;-)


1
A menos que você esteja sendo pago para otimizar algum código em tempo real, para o qual você precisa cortar apenas 1 ou 2 milissegundos para se ajustar às demandas de tempo de execução. É claro que esse é um trabalho para o otimizador, diriam alguns - isto é, se você tiver um otimizador para sua arquitetura.
Neowizard

6

Se você está preocupado com a otimização, deve usar

for (;;)

porque isso não tem testes. (modo cínico)


2
É por isso que é prudente usar while(2), deixa espaço para otimização e você simplesmente muda para for (;;)quando estiver pronto para um aumento de desempenho.
Groo

5

Parece-me que essa é uma daquelas perguntas comportamentais da entrevista mascaradas como uma questão técnica. Algumas empresas fazem isso - elas fazem uma pergunta técnica que deve ser bastante fácil para qualquer programador competente responder, mas quando o entrevistado dá a resposta correta, o entrevistador diz que está errado.

A empresa quer ver como você reagirá nessa situação. Você fica sentado quieto e não insiste que sua resposta está correta, devido à dúvida ou medo de perturbar o entrevistador? Ou você está disposto a desafiar uma pessoa com autoridade que você sabe que está errada? Eles querem ver se você está disposto a defender suas convicções e se pode fazê-lo de maneira diplomática e respeitosa.


3

Eu costumava programar códigos C e Assembly de volta quando esse tipo de absurdo poderia ter feito diferença. Quando fez a diferença, escrevemos na Assembléia.

Se me fizessem essa pergunta, eu repetiria a famosa citação de Donald Knuth, de 1974, sobre otimização prematura e andaria se o entrevistador não risse e seguisse em frente.


1
Dado que ele também disse: "Nas disciplinas de engenharia estabelecidas, uma melhoria de 12%, facilmente obtida, nunca é considerada marginal e acredito que o mesmo ponto de vista deve prevalecer na engenharia de software", acho que você caminha injustamente.
Alice

3

Talvez o entrevistador tenha feito uma pergunta tão estúpida intencionalmente e queira que você faça três pontos:

  1. Raciocínio básico. Ambos os loops são infinitos, é difícil falar sobre desempenho.
  2. Conhecimento sobre níveis de otimização. Ele queria ouvir de você se você permitir que o compilador faça alguma otimização para você, ele otimizará a condição, especialmente se o bloco não estiver vazio.
  3. Conhecimento sobre arquitetura de microprocessador. A maioria das arquiteturas possui uma instrução especial da CPU para comparação com 0 (embora não necessariamente mais rápida).

2

Aqui está um problema: se você realmente escreve um programa e mede sua velocidade, a velocidade de ambos os loops pode ser diferente! Para uma comparação razoável:

unsigned long i = 0;
while (1) { if (++i == 1000000000) break; }

unsigned long i = 0;
while (2) { if (++i == 1000000000) break; }

com algum código adicionado que imprima a hora, algum efeito aleatório, como a posição do loop em uma ou duas linhas de cache, pode fazer a diferença. Por um acaso, um loop pode estar completamente dentro de uma linha de cache, ou no início de uma linha de cache, ou pode ultrapassar duas linhas de cache. E, como resultado, o que quer que o entrevistador afirme ser o mais rápido pode ser mais rápido - por coincidência.

No pior cenário: um compilador otimizador não descobre o que o loop faz, mas descobre que os valores produzidos quando o segundo loop é executado são os mesmos que os produzidos pelo primeiro. E gere código completo para o primeiro loop, mas não para o segundo.


1
O raciocínio "linhas de cache" funcionará apenas em um chip que possui um cache extremamente pequeno.
Sharptooth

10
Isso não vem ao caso. Perguntas sobre velocidade assumem "tudo o resto igual".
Jim Balter

6
@ JimBalter: Esta não era uma questão de velocidade, era uma questão sobre uma discussão com um entrevistador. E a possibilidade de que fazer um teste real possa provar que o entrevistador está "certo" - por razões que nada têm a ver com seu argumento, mas são pura coincidência. O que colocaria você em uma situação embaraçosa se tentasse provar que ele estava errado.
precisa saber é o seguinte

1
@ gnasher729 Todos os argumentos lógicos à parte, vale a pena examinar o caso de coincidência sobre o qual você fala. Mas ainda acreditar cegamente que esse caso existe não é apenas ingênuo, mas estúpido. Contanto que você não explique o que, como ou por que isso aconteceria, essa resposta é inútil.
user568109

4
@ gnasher729 "Não era uma questão de velocidade" - não discutirei pessoas que empregam técnicas retóricas desonestas.
Jim Balter

2

Ambos são iguais - o mesmo.

De acordo com as especificações, qualquer coisa que não seja 0 é considerada verdadeira, portanto, mesmo sem qualquer otimização, e um bom compilador não gerará nenhum código por enquanto (1) ou enquanto (2). O compilador geraria uma verificação simples para != 0.


@ djechlin - porque ambos tomam 1 ciclo de CPU.
UncleKing 23/07

A constante do compilador a dobra, portanto nem é avaliada em tempo de execução.
PaulHK

2

A julgar pela quantidade de tempo e esforço que as pessoas gastaram testando, provando e respondendo a essa pergunta direta, eu diria que as duas coisas ficaram muito lentas ao fazer a pergunta.

E assim, para gastar ainda mais tempo nisso ...

"while (2)" é ridículo, porque,

"while (1)" e "while (true)" são historicamente usados ​​para criar um loop infinito que espera que "break" seja chamado em algum estágio do loop com base em uma condição que certamente ocorrerá.

O "1" está simplesmente lá para sempre avaliar como verdadeiro e, portanto, dizer "while (2)" é tão bobo quanto dizer "while (1 + 1 == 2)", que também será avaliado como true.

E se você quiser ser completamente bobo, use: -

while (1 + 5 - 2 - (1 * 3) == 0.5 - 4 + ((9 * 2) / 4)) {
    if (succeed())
        break;
}

Eu gostaria de pensar que seu codificador cometeu um erro de digitação que não afetou a execução do código, mas se ele intencionalmente usou o "2" para ser esquisito, demiti-lo antes que ele coloque coisas estranhas em todo o seu código, dificultando leia e trabalhe com.


A reversão que você fez reverte a edição por um usuário de alta reputação. Essas pessoas geralmente conhecem muito bem as políticas e estão aqui para ensiná-las a você. Acho que a última linha de sua postagem não adiciona nada útil à sua postagem. Mesmo se eu entendesse a piada que eu obviamente estou sentindo falta, eu a consideraria tagarela demais, não valendo o parágrafo extra.
Palec 29/07

@Palec: Obrigado pelo comentário. Descobri que o mesmo cara editou muitas das minhas postagens, principalmente para remover a assinatura. Então achei que ele era apenas um troll de reputação e, portanto, sua reputação. Os fóruns on-line esgotaram tanto o idioma - e as cortesias comuns - que não mais assinamos nossos escritos? talvez eu seja um pouco velha escola, mas parece rude omitir olá e adeus. Estamos usando aplicativos, mas ainda somos humanos.
ekerner

1
No Stack Exchange , saudações, slogans, agradecimentos etc. não são bem-vindos . O mesmo é mencionado na Central de Ajuda , especificamente sob o comportamento esperado . Nenhuma parte do Stack Exchange , nem mesmo o Stack Overflow , é um fórum - eles são sites de perguntas e respostas. A edição é uma das diferenças. Os comentários também devem servir apenas para fornecer feedback sobre a postagem, não para discussão ( Stack Overflow Chat ). E existem muitas outras maneiras pelas quais as perguntas e respostas diferem de um fórum. Mais sobre isso na Central de Ajuda e no Meta Stack Overflow .
Palec 29/07

Observe que, quando você tem mais de 2000 representantes (privilégio de edição), não recebe mais representantes das edições. Na dúvida, por que e na qual você discorda foi aplicada à sua postagem, ou você vê o mau comportamento de alguém, pergunte no Meta Stack Overflow . Se o editor fizesse a coisa errada, eles poderiam ser notificados por um mod (talvez não percebessem) ou até serem punidos de alguma forma (se o comportamento fosse intencionalmente malicioso). Caso contrário, você receberá dicas para explicar por que a edição / comportamento está correto. A reversão desnecessária atrapalha o histórico de revisões e pode levá-lo à guerra de reversões. Reviro apenas quando tenho certeza de que a edição está incorreta.
Palec 29/07

Finalmente, sobre assinatura, saudação,…: Pode parecer rude não incluir essas formalidades, mas aumenta a taxa de sinal para ruído e nenhuma informação é perdida. Você pode escolher qualquer apelido que desejar, que é a sua assinatura. Na maioria dos lugares, você também tem seu avatar.
Palec 29/07

1

Isso depende do compilador.

Se otimizar o código ou avaliar 1 e 2 como true com o mesmo número de instruções para um conjunto de instruções específico, a velocidade de execução será a mesma.

Em casos reais, sempre será igualmente rápido, mas seria possível imaginar um compilador e um sistema em particular quando isso seria avaliado de maneira diferente.

Quero dizer: essa não é realmente uma questão relacionada ao idioma (C).


0

Como as pessoas que procuram responder a essa pergunta desejam o loop mais rápido, eu teria respondido que ambas estão igualmente compilando o mesmo código de montagem, conforme indicado nas outras respostas. No entanto, você pode sugerir ao entrevistador usando 'loop unrolling'; um loop while {em vez do loop while.

Cauteloso: Você precisa garantir que o loop sempre seja executado uma vez .

O loop deve ter uma condição de interrupção dentro.

Também para esse tipo de loop, eu preferiria pessoalmente o uso de do {} enquanto (42), pois qualquer número inteiro, exceto 0, faria o trabalho.


Por que ele sugeriria um loop do {} while? Enquanto (1) (ou enquanto (2)) faz a mesma coisa.
Catsunami 02/08/19

do while remove um salto extra para um melhor desempenho. Claro que no caso em que você sabe que o laço seria executado pelo menos uma vez
Antonin GAVREL

0

A resposta óbvia é: conforme publicado, os dois fragmentos executariam um loop infinito igualmente ocupado, o que torna o programa infinitamente lento .

Embora a redefinição de palavras-chave C como macros tenha tecnicamente um comportamento indefinido, é a única maneira em que posso pensar para tornar os fragmentos de código mais rápidos: você pode adicionar esta linha acima dos 2 fragmentos:

#define while(x) sleep(x);

de fato, tornará o while(1)dobro da velocidade (ou metade da velocidade) que while(2).


@ MaxB: na verdade, o truque macro deve ser ainda mais distorcido. Eu acho que a nova versão deve funcionar, embora não seja garantida pelo padrão C.
chqrlie

-4

A única razão pela qual consigo pensar por que isso while(2)seria mais lento é:

  1. O código otimiza o loop para

    cmp eax, 2

  2. Quando a subtração ocorre, você está subtraindo essencialmente

    uma. 00000000 - 00000010 cmp eax, 2

    ao invés de

    b. 00000000 - 00000001 cmp eax, 1

cmpapenas define sinalizadores e não define um resultado. Portanto, nos bits menos significativos, sabemos se precisamos pegar emprestado ou não com b . Enquanto que com a você deve executar duas subtrações antes de obter um empréstimo.


15
O cmp levará 1 ciclo de CPU, em ambos os casos.
UncleKing

8
Esse código estaria incorreto. Código correcto se carregar 2 ou 1 em eax registo, em seguida comparar com eax 0.
gnasher729
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.