enquanto (1) vs. para (;;) Existe uma diferença de velocidade?

154

Versão longa...

Um colega de trabalho afirmou hoje depois de ver meu uso while (1)em um script Perl que for (;;)é mais rápido. Argumentei que eles deveriam ser os mesmos, esperando que o intérprete otimize quaisquer diferenças. Configurei um script que executaria 1.000.000.000 de iterações de loop e o mesmo número de loops while e registrei o tempo entre eles. Não pude encontrar nenhuma diferença apreciável. Meu colega de trabalho disse que um professor havia dito que ele while (1)estava fazendo uma comparação 1 == 1e que for (;;)não estava. Repetimos o mesmo teste com 100x o número de iterações com C ++ e a diferença foi insignificante. No entanto, foi um exemplo gráfico de quão rápido o código compilado pode ser em comparação com uma linguagem de script.

Versão curta...

Existe alguma razão para preferir um while (1)over a for (;;)se você precisar de um loop infinito para sair?

Nota: Se não estiver claro a partir da pergunta. Essa foi uma discussão acadêmica puramente divertida entre dois amigos. Estou ciente de que este não é um conceito super importante que todos os programadores deveriam agonizar. Obrigado por todas as ótimas respostas que eu (e tenho certeza que outros) aprendemos algumas coisas com essa discussão.

Atualização: O colega de trabalho acima mencionado pesou com uma resposta abaixo.

Citado aqui, caso seja enterrado.

Ele veio de um programador de montagem AMD. Ele afirmou que os programadores C (o povo) não percebem que seu código tem ineficiências. Ele disse hoje que os compiladores gcc são muito bons e colocam pessoas como ele fora do negócio. Ele disse, por exemplo, e me contou sobre o while 1vs for(;;). Eu o uso agora por hábito, mas o gcc e especialmente os intérpretes farão a mesma operação (um salto no processador) nos dois dias, pois eles são otimizados.

Copas
fonte
4
Estou curioso. Por que precisa de um loop infinito em um script perl? Você obviamente não está programando um motorista ou uma coisa do sistema ... Infinito é calma longa :-)
Luc M
125
Qual loop infinito é mais rápido? LOL ... "Meu novo computador é tão rápido, ele é executado um loop infinito em pouco menos de uma hora ..." ;-)
Arjan Einbu
8
Foi um professor de sociologia que lhe disse isso? Na era moderna, o código digitado não é o que o computador acaba vendo.
22139 brian d foy
5
Espero que a quantidade de tempo que você levou para testar isso foi muito mais longa do que a quantidade de tempo potencialmente economizada por saber qual é a mais rápida, se houver. mesmo que você o amortize durante toda a sua vida útil de programação.
Peter Recore
4
Por que o compilador jamais geraria código para executar um teste que sabe não ter efeitos colaterais e cujo resultado o compilador já conhece? Isso não faz sentido.
David Schwartz

Respostas:

218

No perl, eles resultam nos mesmos opcodes:

$ perl -MO=Concise -e 'for(;;) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

$ perl -MO=Concise -e 'while(1) { print "foo\n" }'
a  <@> leave[1 ref] vKP/REFC ->(end)
1     <0> enter ->2
2     <;> nextstate(main 2 -e:1) v ->3
9     <2> leaveloop vK/2 ->a
3        <{> enterloop(next->8 last->9 redo->4) v ->4
-        <@> lineseq vK ->9
4           <;> nextstate(main 1 -e:1) v ->5
7           <@> print vK ->8
5              <0> pushmark s ->6
6              <$> const[PV "foo\n"] s ->7
8           <0> unstack v ->4
-e syntax OK

Da mesma forma no GCC:

#include <stdio.h>

void t_while() {
    while(1)
        printf("foo\n");
}

void t_for() {
    for(;;)
        printf("foo\n");
}

    .file   "test.c"
    .section    .rodata
.LC0:
    .string "foo"
    .text
.globl t_while
    .type   t_while, @function
t_while:
.LFB2:
    pushq   %rbp
.LCFI0:
    movq    %rsp, %rbp
.LCFI1:
.L2:
    movl    $.LC0, %edi
    call    puts
    jmp .L2
.LFE2:
    .size   t_while, .-t_while
.globl t_for
    .type   t_for, @function
t_for:
.LFB3:
    pushq   %rbp
.LCFI2:
    movq    %rsp, %rbp
.LCFI3:
.L5:
    movl    $.LC0, %edi
    call    puts
    jmp .L5
.LFE3:
    .size   t_for, .-t_for
    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zR"
    .uleb128 0x1
    .sleb128 -8
    .byte   0x10
    .uleb128 0x1
    .byte   0x3
    .byte   0xc
    .uleb128 0x7
    .uleb128 0x8
    .byte   0x90
    .uleb128 0x1
    .align 8
.LECIE1:
.LSFDE1:
    .long   .LEFDE1-.LASFDE1
.LASFDE1:
    .long   .LASFDE1-.Lframe1
    .long   .LFB2
    .long   .LFE2-.LFB2
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI0-.LFB2
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI1-.LCFI0
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE1:
.LSFDE3:
    .long   .LEFDE3-.LASFDE3
.LASFDE3:
    .long   .LASFDE3-.Lframe1
    .long   .LFB3
    .long   .LFE3-.LFB3
    .uleb128 0x0
    .byte   0x4
    .long   .LCFI2-.LFB3
    .byte   0xe
    .uleb128 0x10
    .byte   0x86
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI3-.LCFI2
    .byte   0xd
    .uleb128 0x6
    .align 8
.LEFDE3:
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

Então, acho que a resposta é que são iguais em muitos compiladores. Obviamente, para alguns outros compiladores, isso pode não ser necessariamente o caso, mas é provável que o código dentro do loop seja alguns milhares de vezes mais caro do que o próprio loop, então quem se importa?

bdonlan
fonte
15
tente com B :: Deparse, deparsing um infinito loop retorna um while: P
Kent Fredric
27
"No perl, eles resultam nos mesmos opcodes" ... sim, mas qual é mais rápido? :-)
o Homem de Lata
6
Eu amo que o puts () do gcc substituiu o printf (), já que existe apenas um argumento e, portanto, nada para formatar - mais rápido e mais seguro! (gcc também verifica a formatação de etiquetas contra a lista de argumentos variável.)
Lee D
@o Tin Man: eles são equivalentes, porque o computador faz as mesmas operações exatas: P
BlackBear
1
@ snap, não é 'completamente' incorreto, apenas se concentra nos custos de tempo de execução. Eu não posso imaginar que tipo de situação resultaria no tempo de análise de loops infinitos sendo a chave fator decisivo em quão rápido o seu programa é executado
bdonlan
55

Usando o GCC, os dois parecem compilar para a mesma linguagem assembly:

L2:
        jmp     L2
Martin Cote
fonte
20
Usando GCC com a opção -S (montar, não link)
Martin Cote
54

Não há muitas razões para preferir um ao outro. Eu acho que, while(1)particularmente, while(true)são mais legíveis do que for(;;), mas essa é apenas a minha preferência.

Bill the Lizard
fonte
94
#define EVER ;; para (sempre) eu sempre achei esse tipo de diversão.
2055 Tom
19
Que tal #define ever (;;) para sempre;
Martin Cote
16
Ambos parecem mais legíveis na superfície, mas tento não definir novas palavras-chave para o meu programador de manutenção (geralmente eu) passar a cabeça em cima dele.
Bill o lagarto #
13
@ Martin que não funcionará, porque # define não substitui dentro de um token e foreveré seu próprio token.
Lily Chung
2
"Eu tento não definir novas palavras-chave para minha manutenção" - se apenas mais pessoas adotassem essa atitude, eu não estaria agarrado a todas essas travessuras idiotas e mágicas como truques de mão toda vez que eu me virasse!
precisa saber é
31

Não há diferença de acordo com o padrão. 6.5.3 / 1 tem:

A declaração for

for ( for-init-statement ; conditionopt ; expressionopt ) statement

é equivalente a

{
  for-init-statement
  while ( condition ) {
    statement
    expression ;
  }
}

E 6.5.3 / 2 tem:

Uma ou ambas as condições e a expressão podem ser omitidas. Uma condição ausente torna a cláusula implícita while equivalente a while (true).

Portanto, de acordo com o padrão C ++, o código:

for (;;);

é exatamente o mesmo que:

{
  while (true) {
    ;
    ;
  }
}
Richard Corden
fonte
4
Isso não pertence ao código ou desempenho gerado. O padrão define apenas a funcionalidade. Obviamente, o desempenho será o mesmo.
Potatoswatter 13/08/09
1
Não acredito que seja verdade que uma diferença de desempenho viole a regra do tipo "como se". Se fosse, não seria permitido aos compiladores acelerar o seu código sob a regra como se, por exemplo, reordenando instruções independentes. Os compiladores de fato fazem exatamente isso. Mas minha cópia do padrão está lá em cima.
Steve Jessop
28

O compilador Visual C ++ usado para emitir um aviso para

while (1) 

(expressão constante), mas não para

for (;;)

Continuei a prática de preferir for (;;)por esse motivo, mas não sei se o compilador ainda faz isso atualmente.

sean e
fonte
o aviso é provavelmente becuase você usou while (1) em vez de while (true)
jrharshath
16
verdadeiro é uma constante. while (true) é uma expressão constante. Para qualquer pessoa interessada, o aviso C4127 está documentado aqui: msdn.microsoft.com/en-us/library/6t66728h(VS.80).aspx
sean e
Sim, o aviso ainda está presente para 1 e verdadeiro. Essa é a razão pela qual eu sempre uso for (;;)
Elviss Strazdins
26

for(;;) é um caractere a menos para digitar, se você quiser ir nessa direção para otimizar as coisas.

Chris Bartow
fonte
21
É bom saber para jogar golfe. Caso contrário, é um mau motivo para escolher uma sintaxe.
Adam Bellaire
A dispersão do @AdamBellaire geralmente aumenta a legibilidade, acima de um certo limite de habilidade.
Vector Gorgoth 27/11
20

O Turbo C com esses compiladores antigos for(;;)resulta em um código mais rápido while(1).

Hoje, os compiladores gcc, Visual C (acho que quase todos) otimizam bem, e CPUs com 4,7 MHz raramente são usadas.

Naqueles dias, a for( i=10; i; i-- )era mais rápida do que for( i=1; i <=10; i++ ), porque a comparação ié 0, resulta em um salto condicional de CPU-Zero-Flag. E o Zero-Flag foi modificado com a última operação de decremento ( i-- ), nenhuma operação extra de cmp é necessária.

    call    __printf_chk
    decl    %ebx          %ebx=iterator i 
    jnz     .L2
    movl    -4(%ebp), %ebx
    leave

e aqui com for(i=1; i<=10; i++)cmpl extra:

    call    __printf_chk
    incl    %ebx
    cmpl    $11, %ebx
    jne     .L2
    movl    -4(%ebp), %ebx
    leave
Lutz L.
fonte
13

Para todas as pessoas que discutem, você não deve usar indefinte enquanto faz loops e sugerir coisas idiotas, como usar goto aberto (sério, ai)

while (1) {
     last if( condition1 );
     code();
     more_code(); 
     last if( condition2 ); 
     even_more_code(); 
}

Realmente não pode ser representado efetivamente de nenhuma outra maneira. Não sem criar uma variável de saída e fazer magia negra para mantê-la sincronizada.

Se você tem uma inclinação para a sintaxe mais goto-esque, use algo sensato que limite o escopo.

flow: { 

   if ( condition ){ 
      redo flow;
   }
   if ( othercondition ){ 
       redo flow;
   }
   if ( earlyexit ){ 
       last flow;
   }
   something(); # doesn't execute when earlyexit is true 
}

Em última análise, a velocidade não é tão importante

Preocupar-se com a eficácia da velocidade das diferentes estruturas de loop é um enorme desperdício de tempo. Otimização prematura por completo. Não consigo pensar em nenhuma situação que já vi em que o código de perfil encontrou gargalos na minha escolha de construção de loop.

Geralmente é o como do loop e o quê do loop.

Você deve "otimizar" a legibilidade e a sucessão e escrever o que for melhor para explicar o problema ao próximo otário pobre que encontrar seu código.

Se você usar o truque "goto LABEL" que alguém mencionou, e eu tiver que usar seu código, esteja preparado para dormir com um olho aberto, especialmente se você fizer isso mais de uma vez, porque esse tipo de coisa cria um código espaguete horrível .

Só porque você pode criar código espaguete não significa que você deve

Kent Fredric
fonte
9

De Stroustrup, TC ++ PL (3a edição), §6.1.1:

A notação curiosa for (;;)é a maneira padrão de especificar um loop infinito; você poderia pronunciar "para sempre". [...] while (true)é uma alternativa.

Eu prefiro for (;;).

Hans W
fonte
9

Se o compilador não fizer nenhuma otimização, for(;;)sempre será mais rápido que while(true). Isso ocorre porque a instrução while avalia a condição toda vez, mas a instrução for é um salto incondicional. Mas se o compilador otimizar o fluxo de controle, ele poderá gerar alguns códigos de operação. Você pode ler o código de desmontagem com muita facilidade.

PS, você pode escrever um loop infinito como este:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }
silverbullettt
fonte
Nos dias modernos, a idade NUNCA deve ser substituída por EVS (fala adolescente)! Sério, eu simplesmente uso para (;;) {}. Eu li on-line há muito tempo sobre as diferenças entre os dois (quando eu era mais jovem e realmente não sabia que eles eram os mesmos) e fiquei preso ao que li.
Bja
8

Eu ouvi sobre isso uma vez.

Ele veio de um programador de montagem AMD. Ele afirmou que os programadores C (as pessoas) não percebem que seu código tem ineficiências. Ele disse hoje que os compiladores gcc são muito bons e colocam pessoas como ele fora do negócio. Ele disse, por exemplo, e me contou sobre o while 1vs for(;;). Eu o uso agora por hábito, mas o gcc e especialmente os intérpretes farão a mesma operação (um salto no processador) nos dois dias, pois eles são otimizados.

Jimmie Clark
fonte
5

Em uma construção otimizada de uma linguagem compilada, não deve haver diferença apreciável entre os dois. Nenhum deles deve terminar comparando em tempo de execução; eles apenas executarão o código do loop até você sair manualmente do loop (por exemplo, com a break).

Charlie
fonte
3

Estou surpreso que ninguém testou corretamente for (;;)contra o while (1)perl!

Como o perl é uma linguagem interpretada, o tempo para executar um script perl não consiste apenas na fase de execução (que neste caso é a mesma), mas também na fase de interpretação antes da execução. Ambas as fases devem ser levadas em consideração ao fazer uma comparação de velocidade.

Felizmente, o perl possui um módulo Benchmark conveniente , que podemos usar para implementar um benchmark, como a seguir:

#!/usr/bin/perl -w

use Benchmark qw( cmpthese );

sub t_for   { eval 'die; for (;;) { }'; }
sub t_for2  { eval 'die; for (;;)  { }'; }
sub t_while { eval 'die; while (1) { }'; }

cmpthese(-60, { for => \&t_for, for2 => \&t_for2, while => \&t_while });

Observe que estou testando duas versões diferentes do loop for infinito: uma que é mais curta que o loop while e outra que possui um espaço extra para torná-lo com o mesmo comprimento do loop while.

No Ubuntu 11.04 x86_64 com perl 5.10.1, recebo os seguintes resultados:

          Taxa para for2 enquanto
para 100588 / s - -0% -2%
for2 100937 / s 0% - -1%
enquanto 102147 / s 2% 1% -

O loop while é claramente o vencedor nesta plataforma.

No FreeBSD 8.2 x86_64 com perl 5.14.1:

         Taxa para for2 enquanto
para 53453 / s - -0% -2%
for2 53552 / s 0% - -2%
enquanto 54564 / s 2% 2% -

Enquanto loop é o vencedor aqui também.

No FreeBSD 8.2 i386 com perl 5.14.1:

         Avalie enquanto for2
enquanto 24311 / s - -1% -1%
para 24481 / s 1% - -1%
for2 24637 / s 1% 1% -

Surpreendentemente, o loop for com um espaço extra é a escolha mais rápida aqui!

Minha conclusão é que o loop while deve ser usado na plataforma x86_64 se o programador estiver otimizando a velocidade. Obviamente, um loop for deve ser usado ao otimizar espaço. Infelizmente, meus resultados são inconclusivos em relação a outras plataformas.

estalar
fonte
9
A conclusão é descaradamente errada. Benchmarktem suas limitações e não pode ser usado para distinguir rápido de lento se os resultados estiverem dentro de 7% um do outro. Além disso, você não testou a diferença entre os loops fore whileporque cada sub dieantes de atingir os loops. E desde quando a quantidade de espaço em branco importava para o intérprete Perl? Desculpe, mas a análise é extremamente falha.
Zaid
2
@Zaid, obrigado por seus comentários! Você se importaria de postar sua própria resposta para que todos possam aprender com isso? :) dieExiste no meu código porque minha intenção é testar apenas a diferença de tempo de compilação . Como outros já apontaram, o código de bytes resultante é idêntico, portanto, não faz sentido testar isso. Surpreendentemente, a quantidade de espaço em branco parece fazer uma pequena diferença nesse caso nos meus ambientes de teste. Poderia ter algo a ver com a forma como os personagens acabam ficando alinhado na memória ou algo semelhante ...
agarrar
4
Não preciso postar uma resposta, porque o que eu diria já foi mencionado por bdonlan. E mesmo se você estiver comparando tempos de compilação, os números Benchmarksão inconclusivos. Não confie nessa diferença de 1%!
Zaid
Apenas 60 iterações? Execute testes por 5 minutos, para obter tempos relativos mais precisos.
Mooing Duck
-60executa o teste por 60 segundos.
snap
2

Em teoria, um compilador completamente ingênuo poderia armazenar o literal '1' no binário (desperdiçando espaço) e verificar se 1 == 0 a cada iteração (desperdiçando tempo e mais espaço).

Na realidade, no entanto, mesmo com otimizações "não", os compiladores ainda reduzirão os dois para o mesmo. Eles também podem emitir avisos porque podem indicar um erro lógico. Por exemplo, o argumento de whilepoderia ser definido em outro lugar e você não percebe que é constante.

Nick T
fonte
2

Estou surpreso que ninguém tenha oferecido a forma mais direta, correspondente à montagem desejada:

forever:
     do stuff;
     goto forever;
Phil Miller
fonte
Dose que não termina com o mesmo código de máquina que enquanto 1 ou para (;;) em digamos c?
Copas
1
Uma outra falha dessa abordagem: ela viola o encapsulamento por não incluir o loop em um bloco - portanto, quaisquer variáveis ​​declaradas no loop estão disponíveis fora do loop. (Claro, você poderia { forever: do stuff; goto forever; })
Roy Tinker
2

while(1)é um idioma for(;;)reconhecido pela maioria dos compiladores.

Fiquei feliz em ver que perl também reconhece until(0).

JMD
fonte
Em que situação até (0) seria útil?
Copas
3
até que () seja o oposto de while (), assim como, a menos que () seja o oposto de if (). (! Condição): Como sugerido eslewhere neste segmento, pode-se escrever fazer {algo ...} while Uma alternativa poderia ser até (condição) {} algo
JMD
2

Para resumir o for (;;)vswhile (1) debate Nos compiladores, esses ganhos são otimizados, pois, com o fato de que o último é mais fácil de entender do que o primeiro, acredito que seria mais preferível.

redbandit
fonte
2

Acabei de encontrar esse tópico (apesar de alguns anos atrasado).

Acho que encontrei a verdadeira razão pela qual "para (;;)" é melhor que "enquanto (1)".

de acordo com o "padrão de codificação barr 2018"

Kernighan & Ritchie long ago recommended for (;;) , which has the additional benefit
of insuring against the visually-confusing defect of a while (l); referencing a variable l’.

Basicamente, esse não é um problema de velocidade, mas de legibilidade. Dependendo da fonte / impressão do código, o número um (1) em algum momento pode parecer uma letra minúscula l.

ie 1 vs l. (em algumas fontes, elas parecem idênticas).

Assim, while (1) pode parecer um loop while dependente da variável letra L.

while (true) também pode funcionar, mas em alguns casos mais antigos de C e C incorporados, true / false ainda não estão definidos, a menos que stdbool.h esteja incluído.

Nick Law
fonte
2
Eu diria que o problema no seu código seria que você tem uma variável chamada l, não isso 1e lpode parecer semelhante.
mjuopperi 12/09
Concordo, eu sei que o padrão de codificação Barr também diz em outro lugar que as variáveis ​​devem ter pelo menos três caracteres, mesmo em loops. ou seja, sem i ++ etc. em um loop for. Eu acho que isso pode ser um pouco demais. Ao digitar, também percebo que não é apenas a letra L que se parece com um 1. A letra i, que é comumente usada como variável, também pode causar problemas.
Nick Law
-3

Eu pensaria que ambos são iguais em termos de desempenho. Mas eu preferiria o (1) para facilitar a leitura, mas questiono por que você precisa de um loop infinito.

bichonfrise74
fonte
-14

Eles são os mesmos. Há questões muito mais importantes a serem ponderadas.


Meu argumento, que foi implícito, mas não explicitamente mencionado acima, é que um compilador decente geraria exatamente o mesmo código para ambas as formas de loop. O ponto mais importante é que a construção de loop é uma parte menor do tempo de execução de qualquer algoritmo, e você deve primeiro garantir que otimizou o algoritmo e tudo o mais relacionado a ele. A otimização da construção do loop deve estar absolutamente no final da sua lista de prioridades.

Mark Ransom
fonte
22
Sem links ou explicação. Inútil, subjetivo e um pouco condescendente.
Cdmckay 20/05/09
1
bem, nenhuma prova, mas ele está certo. Ambos chamam o Opcode por pular quando falsos. (o que tornaria o mesmo que Goto, mas ninguém gosta gotos)
Matthew Whited
3
Eu não sabia que apenas perguntas importantes eram feitas, meu erro foi minha primeira pergunta.
2032 Copas
3
Sim, eu admito que é condescendente. Mas, falando sério, mesmo sem nenhuma prova, é óbvio que eles estarão no mesmo estádio rapidamente; se a pergunta fosse sobre estilo, haveria algo para discutir. Eu estava tentando afirmar que, na lista de coisas com que se preocupar, isso realmente deveria estar no final da lista.
Mark Ransom
8
Eu não estava tentando ser um idiota. Eu estava tentando fazer um ponto. Quando publiquei, estava tentando um tipo de humor sombrio, e é óbvio que falhei; por isso peço desculpas.
Mark Ransom