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 while
deve finalmente avaliar para true
ou false
. Nesse caso, ambos avaliam true
e não há instruções condicionais extras dentro da while
condiçã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?
c
performance
while-loop
Nikole
fonte
fonte
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.Respostas:
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:
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=intel
com um sinalizador de otimização):Com
-O0
:Com
-O1
:Com
-O2
e-O3
(mesma saída):De fato, a montagem gerada para o loop é idêntica para todos os níveis de otimização:
Os bits importantes são:
Não consigo ler a montagem muito bem, mas esse é obviamente um loop incondicional. A
jmp
instrução redefine incondicionalmente o programa de volta ao.L2
ró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 ++:Editar:
Curiosamente, mesmo sem otimizações , todos os seguintes loops produziram exatamente a mesma saída (incondicional
jmp
) na montagem:E até para minha surpresa:
As coisas ficam um pouco mais interessantes com as funções definidas pelo usuário:
Na
-O0
, esses dois exemplos realmente chamamx
e executam uma comparação para cada iteração.Primeiro exemplo (retornando 1):
Segundo exemplo (retornando
sqrt(7)
):No entanto,
-O1
acima e acima, ambos produzem o mesmo conjunto que os exemplos anteriores (umjmp
retorno 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 é:
fonte
-O
níveis.jmp
volta ao início ou remover completamente o loop.break
declaração), mas eu apenas testei mesmo assim e não, mesmo com o código dentro do loop, o salto é absoluto e incondicional para amboswhile(1)
ewhile(2)
. Sinta-se à vontade para testar os outros se estiver realmente preocupado.Sim,
while(1)
é muito mais rápido do quewhile(2)
, para um ser humano para ler! Se euwhile(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 escreveuwhile(1)
. O dedo do autor escorregou no teclado? Os mantenedores dessa base de código usamwhile(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 lerwhile(w)
ouwhile(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!
fonte
svn-annotate
ougit 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 ...1
.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!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 inteira1
ewhile (2)
avalia a constante inteira2
; 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:
requer um diagnóstico; um compilador lento não tem a opção de adiar a avaliação
2+2
até 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
Portanto, as expressões constantes relevantes são
1 == 0
e2 == 0
, ambas avaliadas para oint
valor0
. (Essas comparações estão implícitas na semântica dowhile
loop; 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
1
como um caso especial) e, no segundo, poderia gerar uma comparação explícita em tempo de execução equivalente a2 != 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)
ewhile(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 parawhile(2)
, é igualmente legal para um compilador gerar código mais rápido dowhile(1)
que para outra ocorrênciawhile(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 ).
fonte
while
deve 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ícitoexpr != 0
que é 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.<expr> == 0
; é isso que "compara igual a 0" significa em C. Essa comparação faz parte da semântica dowhile
loop. Não há implicação, nem no padrão nem na minha resposta, de que o resultado precise ser comparado0
novamente. (Eu deveria ter escrito, em==
vez de!=
.) Mas essa parte da minha resposta não estava clara e eu a atualizarei.while (2)
,while (pointer)
,while (9.67)
, pelo compilador mais ingênuo, unoptimized, você não vai ver qualquer código gerado que os rendimentos0
ou1
. "Eu deveria ter escrito == ao invés de! =" - não, isso não faria sentido.... == 0
expressão C. O que quero dizer é que tanto o "compara igual a 0" exigido pela descrição padrão doswhile
loops quanto umax == 0
expressão explícita implicam logicamente a mesma operação. E acho que um compilador C dolorosamente ingênuo pode gerar código que gera umint
valor de0
ou1
para qualquerwhile
loop - embora eu não acredite que qualquer compilador real seja tão ingênuo.Espere um minuto. O entrevistador, ele se parecia com esse cara?
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 == 0
e2 == 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 .
fonte
cmp
operando 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 velocidadecmp
? 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).cmp
operando 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 ondewhile(1)
é mais rápida quewhile(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!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
o entrevistador diria
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:
Com as otimizações ativadas:
Portanto, o código gerado é exatamente o mesmo, pelo menos com um compilador de otimização.
fonte
while
muito tempo o precedeu) e o operando dewhile
não é booleano ou _Bool ou qualquer outro tipo específico. O operando dewhile
pode ser qualquer expressão ... o while quebra em 0 e continua em não-0.while (00000001) {}
parawhile (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 ++).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:
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 owhile(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) { }
ouwhile(3) { }
seria igualmente rápido ou sewhile(32) { }
seria ainda mais lento .fonte
cmp
algoritmo de uma CPU é uma verificação de bits linear com saída de loop antecipada na primeira diferença.-O0
saí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.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 .É 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.
fonte
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.
fonte
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.
fonte
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 ofor(;;)
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:
while(1)
ewhile(2)
while(1)
porque eles são considerados idiomáticos. Isso deixaria owhile(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)
ewhile(2)
o mesmo construto é um sinal de otimização de baixa qualidade, pois esses são construtos equivalentes.fonte
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 :)
fonte
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. ;-)
fonte
Se você está preocupado com a otimização, deve usar
porque isso não tem testes. (modo cínico)
fonte
while(2)
, deixa espaço para otimização e você simplesmente muda parafor (;;)
quando estiver pronto para um aumento de desempenho.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.
fonte
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.
fonte
Talvez o entrevistador tenha feito uma pergunta tão estúpida intencionalmente e queira que você faça três pontos:
fonte
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:
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.
fonte
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
.fonte
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: -
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.
fonte
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).
fonte
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.
fonte
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:
de fato, tornará o
while(1)
dobro da velocidade (ou metade da velocidade) quewhile(2)
.fonte
A única razão pela qual consigo pensar por que isso
while(2)
seria mais lento é:O código otimiza o loop para
cmp eax, 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
cmp
apenas 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.fonte