c++ perl optimization performance

c++ - mientras(1) vs. para(;;) ¿Hay una diferencia de velocidad?



perl optimization (19)

Versión larga...

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

Version corta...

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

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

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

Citado aquí en caso de que sea enterrado.

Venía de un programador de ensambles de AMD. Él declaró que los programadores de C (la gente) no se dan cuenta de que su código tiene ineficiencias. Dijo que hoy, sin embargo, los compiladores de gcc son muy buenos y ponen a gente como él fuera del negocio. Dijo, por ejemplo, y me dijo sobre el while 1 vs for(;;) . Lo uso ahora por costumbre, pero gcc y especialmente los intérpretes harán la misma operación (un salto de procesador) para ambos estos días, ya que están optimizados.


¡Me sorprende que nadie haya probado adecuadamente for (;;) versus while (1) en Perl!

Como perl es un lenguaje interpretado, el tiempo para ejecutar un script de 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 un punto de referencia como el 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 infinito para el ciclo: uno que es más corto que el ciclo while y otro que tiene un espacio extra para hacerlo de la misma longitud que el ciclo while.

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

Rate for for2 while for 100588/s -- -0% -2% for2 100937/s 0% -- -1% while 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:

Rate for for2 while for 53453/s -- -0% -2% for2 53552/s 0% -- -2% while 54564/s 2% 2% --

While loop es el ganador aquí también.

En FreeBSD 8.2 i386 con perl 5.14.1:

Rate while for for2 while 24311/s -- -1% -1% for 24481/s 1% -- -1% for2 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 debería usarse en la plataforma x86_64 si el programador está optimizando la velocidad. Obviamente, se debe usar un bucle for al optimizar el espacio. Mis resultados lamentablemente no son concluyentes con respecto a otras plataformas.


Creo que ambos son iguales en términos de rendimiento. Pero preferiría (1) que sea legible, pero me pregunto por qué necesita un ciclo infinito.


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

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

Yo prefiero for (;;) .


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

while (1)

(expresión constante) pero no para

for (;;)

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


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


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

Del mismo modo 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 lo más probable es que el código dentro del ciclo sea unos miles de veces más caro que el bucle en sí, de todos modos, ¿a quién le importa?


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

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


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


Escuché sobre esto una vez.

Venía de un programador de ensambles de AMD. Él afirmó que los programadores C (las personas) no se dan cuenta de que su código tiene ineficiencias. Dijo que hoy, sin embargo, los compiladores de gcc son muy buenos y ponen a gente como él fuera del negocio. Dijo, por ejemplo, y me dijo sobre el while 1 vs for(;;) . Lo uso ahora por costumbre, pero gcc y especialmente los intérpretes harán la misma operación (un salto de procesador) para ambos estos días, ya que están optimizados.


Me sorprende que nadie haya ofrecido la forma más directa, correspondiente a la asamblea deseada:

forever: do stuff; goto forever;


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

El enunciado 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 ser omitidas. Una condición faltante hace que la cláusula while implícita sea equivalente a while (verdadero).

Entonces, según el estándar C ++, el código:

for (;;);

es exactamente lo mismo que:

{ while (true) { ; ; } }


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.


Para resumir el debate for (;;) versus while (1) , es obvio que el primero era más rápido en los días de compiladores antiguos que no optimizaban, es por eso que usted tiende a verlo en bases de códigos anteriores, como el código fuente de los Leones Unix. Comentario, sin embargo, en la era de los compiladores de optimización rudo, esas ganancias se optimizan, lo que se combina con el hecho de que este último es más fácil de entender que el primero. Creo que sería más preferible.


Para todas las personas que argumentan que no deberías usar indefinte while loops, y sugerir cosas tontas como usar open goto ''s (en serio, ouch)

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 tiene una inclinación por la sintaxis más goto-esque, use algo sano 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 definitiva, la velocidad no es tan importante

Preocuparse por cuán efectivos sean los diferentes constructos de bucle de velocidad de velocidad es una gran 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 creación de perfiles haya encontrado cuellos de botella en mi elección de constructo de bucle.

En general, es el cómo del bucle y el qué del bucle.

Debería "optimizar" la legibilidad y la concisión, y escribir lo que sea mejor para explicar el problema al siguiente pobre imbécil que encuentre su código.

Si usas el truco "goto LABEL" que alguien mencionó, y tengo que usar tu código, prepárate para dormir con un ojo abierto, especialmente si lo haces más de una vez, porque ese tipo de cosas crea horriblemente el código de spaghetti.

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


Si el compilador no realiza ninguna optimización, for(;;) siempre sería más rápido que while(true) . Esto es porque while-statement evalúa la condición cada vez, pero for-statement 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 desensamblaje muy fácilmente.

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

#define EVER ;; //... for (EVER) { //... }


Turbo C con este viejo compilador for(;;) da como resultado un código más rápido luego while(1) .

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

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

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

y aquí con for(i=1; i<=10; i++) con cmpl extra:

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


Usando GCC, ambos parecen compilar al mismo lenguaje ensamblador:

L2: jmp L2


for(;;) es un carácter menos para escribir si desea ir en esa dirección para optimizar las cosas.


while(1) es una expresión idiomática de for(;;) que es reconocida por la mayoría de los compiladores.

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