mientras que (1) vs. para (;;) ¿Hay una diferencia de velocidad?

154

Versión larga...

Un compañero de trabajo afirmó hoy después de ver mi uso while (1)en un script de Perl que for (;;)es más rápido. Argumenté que deberían ser los mismos con la esperanza de que el intérprete optimizara cualquier diferencia. Configuré un script que ejecutaría 1,000,000,000 para iteraciones de bucles y la misma cantidad de bucles while y registraría el tiempo entre ellos. No pude encontrar una diferencia apreciable. Mi compañero de trabajo dijo que un profesor le había dicho que while (1)estaba haciendo una comparación 1 == 1y que for (;;)no. Repetimos la misma prueba con 100x el número de iteraciones con C ++ y la diferencia fue insignificante. Sin embargo, fue un ejemplo gráfico de cuánto más rápido puede ser el código compilado frente a un lenguaje de script.

Version corta...

¿Hay alguna razón para preferir un while (1)sobre a for (;;)si necesita un bucle infinito para salir?

Nota: Si no está claro en la pregunta. Esta fue una discusión académica puramente divertida entre un par de amigos. Soy consciente de que este no es un concepto súper importante que todos los programadores deberían agonizar. Gracias por todas las excelentes respuestas que (y estoy seguro de que otros) he aprendido algunas cosas de esta discusión.

Actualización: el mencionado compañero de trabajo intervino con una respuesta a continuación.

Citado aquí en caso de que quede enterrado.

Provenía de un programador de ensamblaje de AMD. Afirmó que los programadores de C (la gente) no se dan cuenta de que su código tiene ineficiencias. Sin embargo, dijo hoy que los compiladores de gcc son muy buenos y dejan a personas como él fuera del negocio. Dijo por ejemplo, y me habló de la while 1frente for(;;). Lo uso ahora por costumbre, pero gcc y especialmente los intérpretes harán la misma operación (un salto de procesador) durante estos dos días, ya que están optimizados.

Copas
fuente
44
Soy curioso. ¿Por qué necesita un bucle infinito en un script perl? Obviamente no estás programando un controlador o una cosa del sistema ... Infinite es silencioso por mucho tiempo :-)
Luc M
125
¿Qué bucle infinito es el más rápido? LOL ... "Mi nuevo equipo es tan rápido, se ejecuta un bucle infinito en poco menos de una hora ..." ;-)
Arjan Einbu
8
¿Fue un profesor de sociología quien le dijo eso? En la era moderna, el código que escribe no es lo que la computadora termina viendo.
brian d foy
55
Supongo que la cantidad de tiempo que le tomó probar esto fue mucho más larga que la cantidad de tiempo potencialmente ahorrada al saber cuál es más rápido, si es que lo hace. incluso si lo amortizas durante tus vidas de programación.
Peter Recore
44
¿Por qué el compilador generaría código para realizar una prueba que sabe que no tiene efectos secundarios y cuyo resultado el compilador ya sabe? Eso no tiene sentido.
David Schwartz

Respuestas:

218

En perl, dan como resultado los mismos códigos de operación:

$ 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

Asimismo en 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

Así que supongo que la respuesta es que son los mismos en muchos compiladores. Por supuesto, para algunos otros compiladores esto puede no ser necesariamente el caso, pero es probable que el código dentro del bucle sea unos miles de veces más costoso que el bucle en sí mismo, entonces, ¿a quién le importa?

bdonlan
fuente
15
intente con B :: Deparse, la eliminación de un bucle for infinito devuelve un bucle while: P
Kent Fredric
27
"En perl, dan como resultado los mismos códigos de operación" ... sí, pero ¿cuál es más rápido? :-)
The Tin Man
66
Me encanta que gcc sustituya la opción put () por printf (), ya que solo hay un argumento y, por lo tanto, nada que formatear: ¡más rápido y más seguro! (gcc también verifica las etiquetas de formato con la lista de argumentos variables).
Lee D
@ The Tin Man: son equivalentes, porque la computadora realiza exactamente las mismas operaciones: P
BlackBear
1
@snap, no es "completamente" incorrecto, solo se enfoca en los costos de tiempo de ejecución. No me puedo imaginar qué tipo de situación daría lugar a la hora de analizar los bucles infinitos que son la clave factor decisivo en la rapidez con sus ejecuciones del programa
bdonlan
55

Usando GCC, ambos parecen compilarse en el mismo lenguaje ensamblador:

L2:
        jmp     L2
Martin Cote
fuente
20
Uso de GCC con la opción -S (ensamblar, no vincular)
Martin Cote
54

No hay muchas razones para preferir uno sobre el otro. Creo que, while(1)y particularmente, while(true)son más legibles que for(;;), pero esa es solo mi preferencia.

Bill el lagarto
fuente
94
#define EVER ;; para (NUNCA) Siempre he encontrado ese tipo de diversión.
Tom
19
¿Qué tal #definir siempre (;;) para siempre;
Martin Cote
16
Ambas parecen más legibles en la superficie, pero trato de no definir nuevas palabras clave para que mi programador de mantenimiento (generalmente yo) raspe su cabeza.
Bill the Lizard
13
@Martin que no funcionará, porque # define no reemplaza dentro de un token, y foreveres su propio token.
Lily Chung
2
"Trato de no definir nuevas palabras clave para mi mantenimiento". ¡Si solo más personas tuvieran esa actitud, no me aferraría a todas estas travesuras tontas y mágicas de juego de manos cada vez que me diera la vuelta!
tchrist
31

No hay diferencia según el estándar. 6.5.3 / 1 tiene:

La declaración for

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

es equivalente a

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

Y 6.5.3 / 2 tiene:

Cualquiera o ambas condiciones y la expresión pueden omitirse. Una condición faltante hace que la cláusula while implícita sea equivalente a while (verdadero).

Entonces, de acuerdo con el estándar C ++, el código:

for (;;);

es exactamente lo mismo que:

{
  while (true) {
    ;
    ;
  }
}
Richard Corden
fuente
44
Eso no pertenece al código generado o al rendimiento en absoluto. El estándar solo define la funcionalidad. Por supuesto, el rendimiento será el mismo.
Potatoswatter
1
No creo que sea cierto que una diferencia en el rendimiento viole la regla de si-como. Si lo fuera, entonces los compiladores no tendrían permitido acelerar su código bajo la regla as-if, por ejemplo, reordenando declaraciones independientes. Los compiladores, de hecho, hacen exactamente eso. Pero mi copia de la norma está arriba.
Steve Jessop
28

El compilador de Visual C ++ solía emitir una advertencia para

while (1) 

(expresión constante) pero no para

for (;;)

Continué con la práctica de preferir for (;;)por esa razón, pero no sé si el compilador todavía lo hace en estos días.

sean e
fuente
la advertencia es probablemente porque usó while (1) en lugar de while (verdadero)
jrharshath
16
La verdad es una constante. while (verdadero) es una expresión constante. Para cualquier persona interesada, la advertencia C4127 está documentada aquí: msdn.microsoft.com/en-us/library/6t66728h(VS.80).aspx
sean e
Sí, la advertencia aún está presente para 1 y verdadero. Esa es la razón por la que siempre uso para (;;)
Elviss Strazdins
26

for(;;) es un personaje menos para escribir si quieres ir en esa dirección para optimizar las cosas.

Chris Bartow
fuente
21
Es bueno saberlo para jugar al golf. De lo contrario, una mala razón para elegir una sintaxis.
Adam Bellaire
@AdamBellaire Terseness a menudo aumenta la legibilidad, por encima de un cierto umbral de habilidad.
Vector Gorgoth
20

Turbo C con estos viejos compiladores for(;;)resulta en un código más rápido entonces while(1).

Hoy en día, los compiladores gcc, Visual C (creo que casi todos) se optimizan bien, y las CPU con 4.7 MHz rara vez se usan.

En aquellos días, a for( i=10; i; i-- )era más rápido que for( i=1; i <=10; i++ ), porque comparar ies 0, resulta en un salto condicional CPU-Zero-Flag. Y la Bandera Cero se modificó con la última operación de disminución ( i-- ), no se necesita ninguna operación cmp adicional.

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

y aquí con 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.
fuente
13

Para todas las personas que argumentan que no deberías usar bucles while indefinidos, y que sugieren cosas tontas como usar goto 's abiertos (en serio, ¡ay!)

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

Realmente no se puede representar efectivamente de otra manera. No sin crear una variable de salida y hacer magia negra para mantenerla sincronizada.

Si le gusta la sintaxis más goto-esque, use algo sensato que limite el alcance.

flow: { 

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

En última instancia, la velocidad no es tan importante

Preocuparse por cuán efectivas son las construcciones de bucle diferentes en cuanto a velocidad es una enorme pérdida de tiempo. Optimización prematura de principio a fin. No puedo pensar en ninguna situación que haya visto en la que el código de perfil encontró cuellos de botella en mi elección de construcción en bucle.

Generalmente es el cómo del bucle y el qué del bucle.

Debe "optimizar" la legibilidad y la concisión, y escribir lo que sea mejor para explicar el problema al próximo tonto que encuentre su código.

Si usa el truco "ir a LA ETIQUETA" que alguien mencionó, y tengo que usar su código, prepárese para dormir con un ojo abierto, especialmente si lo hace más de una vez, porque ese tipo de cosas crea un espantoso código de espagueti.

El hecho de que pueda crear código de espagueti no significa que deba

Kent Fredric
fuente
9

De Stroustrup, TC ++ PL (3a edición), §6.1.1:

La notación curiosa for (;;)es la forma estándar de especificar un bucle infinito; puedes pronunciarlo "para siempre". [...] while (true)es una alternativa.

Yo prefiero for (;;).

Hans W
fuente
9

Si el compilador no realiza ninguna optimización, for(;;)siempre sería más rápido quewhile(true) . Esto se debe a que while-instrucción evalúa la condición cada vez, pero for-instrucción es un salto incondicional. Pero si el compilador optimiza el flujo de control, puede generar algunos códigos de operación. Puede leer el código de desmontaje muy fácilmente.

PD: podrías escribir un bucle infinito como este:

#define EVER ;;
  //...
  for (EVER) {
    //...
  }
silverbullettt
fuente
¡En los tiempos modernos, la edad nunca debería ser reemplazada por EVS (habla adolescente)! En serio, solo uso para (;;) {}. Hace mucho tiempo leí en línea sobre las diferencias entre los dos (cuando era más joven y en realidad no sabía que eran lo mismo) y simplemente me quedé con lo que leí.
Bja
8

Escuché sobre esto una vez.

Provenía de un programador de ensamblaje de AMD. Dijo que los programadores de C (las personas) no se dan cuenta de que su código tiene ineficiencias. Sin embargo, dijo hoy que los compiladores de gcc son muy buenos y dejan a personas como él fuera del negocio. Dijo por ejemplo, y me habló de la while 1frente for(;;). Lo uso ahora por costumbre, pero gcc y especialmente los intérpretes harán la misma operación (un salto de procesador) durante estos dos días, ya que están optimizados.

Jimmie Clark
fuente
5

En una compilación optimizada de un lenguaje compilado, no debería haber una diferencia apreciable entre los dos. Ninguno de los dos debe terminar realizando comparaciones en tiempo de ejecución, solo ejecutarán el código del bucle hasta que salga manualmente del bucle (por ejemplo, con a break).

Charlie
fuente
3

¡Me sorprende que nadie haya probado adecuadamente en for (;;)comparación while (1)con Perl!

Debido a que perl es un lenguaje interpretado, el tiempo para ejecutar un script perl no solo consiste en la fase de ejecución (que en este caso es la misma) sino también en la fase de interpretación antes de la ejecución. Ambas fases deben tenerse en cuenta al hacer una comparación de velocidad.

Afortunadamente, Perl tiene un conveniente módulo de referencia que podemos usar para implementar una referencia como la siguiente:

#!/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 });

Tenga en cuenta que estoy probando dos versiones diferentes del bucle infinito: una que es más corta que el bucle while y otra que tiene un espacio adicional para que tenga la misma longitud que el bucle while.

En Ubuntu 11.04 x86_64 con perl 5.10.1 obtengo los siguientes resultados:

          Tasa para for2 while
para 100588 / s - -0% -2%
para2 100937 / s 0% - -1%
mientras que 102147 / s 2% 1% -

El ciclo while es claramente el ganador en esta plataforma.

En FreeBSD 8.2 x86_64 con perl 5.14.1:

         Tasa para for2 while
para 53453 / s - -0% -2%
para2 53552 / s 0% - -2%
mientras 54564 / s 2% 2% -

Mientras que loop es el ganador aquí también.

En FreeBSD 8.2 i386 con perl 5.14.1:

         Califica mientras for2
mientras 24311 / s - -1% -1%
para 24481 / s 1% - -1%
para2 24637 / s 1% 1% -

¡Sorprendentemente, el bucle for con un espacio extra es la opción más rápida aquí!

Mi conclusión es que el ciclo while debe usarse en la plataforma x86_64 si el programador está optimizando la velocidad. Obviamente, se debe usar un bucle for al optimizar el espacio. Lamentablemente, mis resultados no son concluyentes con respecto a otras plataformas.

chasquido
fuente
9
La conclusión es descaradamente errónea. Benchmarktiene sus limitaciones y no se puede usar para distinguir rápido de lento si los resultados están dentro del 7% entre sí. Además, no ha probado la diferencia entre los bucles fory whileporque cada sub lo hará dieantes de llegar a los bucles. ¿Y desde cuándo la cantidad de espacios en blanco era importante para el intérprete de Perl? Lo siento, pero el análisis es extremadamente defectuoso.
Zaid
2
@Zaid, ¡Gracias por tus comentarios! ¿Te importaría publicar tu propia respuesta para que todos puedan aprender de eso? :) dieExiste en mi código porque mi intención es probar solo la diferencia de tiempo de compilación . Como otros ya han señalado, el código de bytes resultante es idéntico, por lo tanto, no tiene sentido probar eso. Sorprendentemente, la cantidad de espacio en blanco parece hacer una pequeña diferencia en este caso en mis entornos de prueba. Que podría tener algo que ver con cómo los personajes terminan siendo alineado en la memoria o algo similar ...
broche
44
No necesito publicar una respuesta porque lo que diría ya ha sido mencionado por bdonlan. E incluso si compara tiempos de compilación, los números Benchmarkno son concluyentes. ¡No confíes en esa diferencia del 1% en absoluto!
Zaid
¿Solo 60 iteraciones? Realice pruebas durante unos 5 minutos para obtener tiempos relativos más precisos.
Mooing Duck
-60ejecuta la prueba durante 60 segundos.
snap
2

En teoría, un compilador completamente ingenuo podría almacenar el literal '1' en el binario (pérdida de espacio) y verificar si 1 == 0 en cada iteración (pérdida de tiempo y más espacio).

En realidad, sin embargo, incluso con optimizaciones "no", los compiladores seguirán reduciendo ambos a la misma. También pueden emitir advertencias porque podría indicar un error lógico. Por ejemplo, el argumento de whilepodría definirse en otro lugar y no te das cuenta de que es constante.

Nick T
fuente
2

Me sorprende que nadie haya ofrecido la forma más directa, correspondiente al ensamblaje deseado:

forever:
     do stuff;
     goto forever;
Phil Miller
fuente
¿Dosis que no terminan con el mismo código de máquina que while 1 o for (;;) en say c?
Copas
1
Otro inconveniente de ese enfoque: viola la encapsulación al no encerrar el bucle en un bloque; por lo tanto, cualquier variable declarada en el bucle está disponible fuera del bucle. (Por supuesto que podrías { forever: do stuff; goto forever; })
Roy Tinker
2

while(1)es un idioma para el for(;;)cual es reconocido por la mayoría de los compiladores.

Me alegró ver que Perl también lo reconoce until(0).

JMD
fuente
¿En qué situación sería útil hasta (0)?
Copas
3
until () es lo opuesto a while () igual que a menos que () sea lo opuesto a if (). Como se sugiere en otra parte de este hilo, uno podría escribir: do {algo ...} while (! Condición) Una alternativa podría ser hasta (condición) {algo}
JMD
2

Para resumir el debate for (;;)vs while (1)es obvio que el primero fue más rápido en los días de los compiladores no optimizadores más antiguos, es por eso que tiende a verlo en bases de código más antiguas, como los comentarios de código fuente de Lions Unix, sin embargo, en la era de la optimización rudo Los compiladores de esas ganancias se optimizan combinando eso con el hecho de que este último es más fácil de entender que el primero, creo que sería más preferible.

redbandit
fuente
2

Acabo de encontrar este hilo (aunque algunos años tarde).

Creo que encontré la razón real por la cual "for (;;)" es mejor que "while (1)".

de acuerdo con el "barr coding standard 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’.

Básicamente, este no es un problema de velocidad, sino un problema de legibilidad. Dependiendo de la fuente / impresión del código, el número uno (1) en un momento puede parecer una letra minúscula l.

es decir, 1 vs l. (en algunas fuentes se ven idénticas).

Por lo tanto, while (1) puede parecer un ciclo while dependiente de la letra variable L.

while (verdadero) también puede funcionar, pero en algunos casos antiguos de C y C incrustados, verdadero / falso aún no están definidos a menos que se incluya stdbool.h.

Nick Law
fuente
2
Yo diría que el problema en su código sería que tiene una variable llamada l, no esa, 1y lpuede tener un aspecto similar.
mjuopperi
De acuerdo, sé que el estándar de codificación Barr también dice en otro lugar que las variables deben tener al menos 3 caracteres incluso para los bucles. es decir, no i ++ etc. en un bucle for. Sin embargo, tiendo a pensar que puede ser un poco demasiado. Mientras escribo, también noto que no es solo la letra L la que parece un 1. La letra i, que se usa comúnmente como variable, también puede causar problemas.
Nick Law
-3

Creo que ambos son iguales en términos de rendimiento. Pero preferiría while (1) para facilitar la lectura, pero me pregunto por qué necesita un bucle infinito.

bichonfrise74
fuente
-14

Ellos son iguales. Hay preguntas mucho más importantes para reflexionar.


Mi punto que estaba implícito pero no se hizo explícitamente arriba, es que un compilador decente generaría exactamente el mismo código para ambas formas de bucle. El punto más importante es que la construcción en bucle es una parte menor del tiempo de ejecución de cualquier algoritmo, y primero debe asegurarse de haber optimizado el algoritmo y todo lo demás relacionado con él. La optimización de su construcción de bucle debería estar absolutamente al final de su lista de prioridades.

Mark Ransom
fuente
22
Sin enlaces ni explicaciones. Inútil, subjetivo y un poco condescendiente.
cdmckay
1
bueno, no hay pruebas pero tiene razón. Ambos llaman al Opcode para saltar cuando es falso. (lo que haría lo mismo que Goto, pero a nadie le gusta GOTOS)
Mateo Whited
3
No sabía que solo las preguntas importantes se debían hacer, mi error fue mi primera pregunta.
Copas
3
Sí, admito que es condescendiente. Pero en serio, incluso sin ninguna prueba, es obvio que van a estar en el mismo estadio rápidamente; Si la pregunta fuera sobre el estilo, habría algo sobre lo que discutir. Estaba tratando de señalar que en la lista de cosas por las que preocuparse, esto realmente debería estar al final de la lista.
Mark Ransom
8
No estaba tratando de ser un idiota. Estaba tratando de hacer un punto. Cuando lo publiqué, intentaba una especie de humor negro, y es obvio que fallé; Por eso me disculpo.
Mark Ransom