¿Cuál es más rápido: while (1) o while (2)?

587

Esta fue una pregunta de entrevista realizada por un gerente superior.

¿Cual es mas rápido?

while(1) {
    // Some code
}

o

while(2) {
    //Some code
}

Dije que ambos tienen la misma velocidad de ejecución, ya que la expresión en el interior whilefinalmente debería evaluar a trueo false. En este caso, ambos evalúan truey no hay instrucciones condicionales adicionales dentro de la whilecondición. Entonces, ambos tendrán la misma velocidad de ejecución y prefiero while (1).

Pero el entrevistador dijo con confianza: "Revise sus conceptos básicos. while(1)Es más rápido que while(2)". (No estaba probando mi confianza)

¿Es esto cierto?

Ver también: ¿"for (;;)" es más rápido que "while (TRUE)"? Si no, ¿por qué la gente lo usa?

Nikole
fuente
202
Un compilador medio decente optimizará ambas formas a la nada.
64
En la compilación optimizada todo el tiempo (n), n! = 0 o for (;;) se traducirá al bucle sin fin de ensamblaje con etiqueta al principio y goto al final. Exactamente el mismo código, el mismo rendimiento.
Alex F
6060
No es sorprendente, un stock optimizado trae 0x100000f90: jmp 0x100000f90(la dirección varía, obviamente) para ambos fragmentos. El entrevistador probablemente se cubrió en una prueba de registro frente a un simple salto marcado. Tanto la pregunta como su suposición son poco convincentes.
WhozCraig
50
Esta pregunta del entrevistador se encuentra bajo los mismos auspicios que dilbert.com/strips/comic/1995-11-17 : conocerá a alguien que realmente cree lo que está diciendo, independientemente del cociente de estupidez en su declaración. Simplemente elija entre lo siguiente: una respiración profunda, jurar, reír, llorar, alguna combinación de lo anterior :)
GMasucci
3
@ Mike W: uno puede preguntarse qué debería hacer un compilador: traducir a una declaración de detención, o considerar que el ciclo sale después de un tiempo infinito y optimizar el retraso infinito.
Yves Daoust

Respuestas:

681

Ambos bucles son infinitos, pero podemos ver cuál toma más instrucciones / recursos por iteración.

Usando gcc, compilé los dos siguientes programas para ensamblar en diferentes niveles de optimización:

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


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

Incluso sin optimizaciones (-O0 ), el ensamblado generado fue idéntico para ambos programas . Por lo tanto, no hay diferencia de velocidad entre los dos bucles.

Como referencia, aquí está el ensamblaje generado (usando gcc main.c -S -masm=intelcon un indicador de optimización):

Con -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"

Con -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"

Con -O2y-O3 (misma salida):

    .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 hecho, el ensamblaje generado para el bucle es idéntico para cada nivel de optimización:

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

Los bits importantes son:

.L2:
    jmp .L2

No puedo leer muy bien el ensamblaje, pero obviamente este es un ciclo incondicional. La jmpinstrucción restablece incondicionalmente el programa a.L2 etiqueta sin siquiera comparar un valor con verdadero, y por supuesto lo vuelve a hacer de inmediato hasta que el programa finalice de alguna manera. Esto corresponde directamente al código C / C ++:

L2:
    goto L2;

Editar:

Curiosamente, incluso sin optimizaciones , los siguientes bucles produjeron exactamente el mismo resultado (incondicional jmp) en el ensamblaje:

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 incluso para mi asombro:

#include<math.h>

while(sqrt(7)) {}

while(hypot(3,4)) {}

Las cosas se ponen un poco más interesantes con las funciones definidas por el usuario:

int x(void) {
    return 1;
}

while(x()) {}


#include<math.h>

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

while(x()) {}

En -O0, estos dos ejemplos realmente llamanx y realizan una comparación para cada iteración.

Primer ejemplo (devolviendo 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 ejemplo (regresando 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"

Sin embargo, en -O1y arriba, ambos producen el mismo ensamblaje que los ejemplos anteriores (un jmpregreso incondicional a la etiqueta anterior).

TL; DR

Bajo GCC, los diferentes bucles se compilan en un ensamblaje idéntico. El compilador evalúa los valores constantes y no se molesta en realizar ninguna comparación real.

La moraleja de la historia es:

  • Existe una capa de traducción entre el código fuente de C ++ y las instrucciones de la CPU, y esta capa tiene implicaciones importantes para el rendimiento.
  • Por lo tanto, el rendimiento no puede evaluarse solo mirando el código fuente.
  • El compilador debe ser lo suficientemente inteligente como para optimizar estos casos triviales. Los programadores no deberían perder el tiempo pensando en ellos en la gran mayoría de los casos.
Acercarse Oscuridad Peces
fuente
196
Tal vez el entrevistador no estaba usando gcc
MM
106
@Matt McNabb Ese es un buen punto, pero si el entrevistador confiaba en optimizaciones específicas del compilador, entonces deben ser muy explícitos al respecto en su pregunta, y deben aceptar la respuesta "no hay diferencia" como correcta para algunos (¿la mayoría?) compiladores.
Jonathan Hartley
109
En aras de eliminar cualquier duda, probé esto en clang 3.4.2, y ambos bucles producen el mismo ensamblaje en todos los -Oniveles.
Martin Tournoij
16
No me parece sorprendente, ya que todo lo que ha colocado en la sección condicional de los bucles son constantes de tiempo de compilación. Como tal, sospecharía que un compilador vería que los bucles siempre serán verdaderos o falsos y respetuosamente simplemente jmpvolverán al principio o eliminarán el bucle por completo.
sherrellbc
10
@hippietrail No veo cómo el contenido (o la falta del mismo) del bucle podría afectar estas optimizaciones (excepto la posibilidad de cualquier breakdeclaración), pero lo probé de todos modos y no, incluso con el código dentro del bucle, el salto es absoluta e incondicional para ambos while(1)y while(2). Siéntase libre de probar a los demás si está realmente preocupado.
ApproachingDarknessFish
282

Sí, while(1)es mucho más rápido que while(2), para un ser humano a leer! Si veo while(1)en una base de código desconocida, inmediatamente sé lo que pretendía el autor, y mis globos oculares pueden continuar a la siguiente línea.

Si yo veo while(2) , probablemente me detendré y trataré de averiguar por qué el autor no escribió while(1). ¿Se deslizó el dedo del autor sobre el teclado? ¿Los mantenedores de esta base de código usan while(n)como un oscuro mecanismo de comentarios para hacer que los bucles se vean diferentes? ¿Es una solución cruda para una advertencia espuria en alguna herramienta de análisis estático roto? ¿O es esta una pista de que estoy leyendo el código generado? ¿Es un error resultante de un mal aconsejado de buscar y reemplazar todo, o una mala fusión, o un rayo cósmico? Tal vez se supone que esta línea de código haga algo dramáticamente diferente. Tal vez se suponía que debía leer while(w)o while(x2). Será mejor que encuentre al autor en el historial del archivo y le envíe un correo electrónico "WTF" ... y ahora he roto mi contexto mental.while(2)while(1) habría tomado una fracción de segundo!

Estoy exagerando, pero solo un poco. La legibilidad del código es realmente importante. ¡Y eso vale la pena mencionarlo en una entrevista!

Chris Culter
fuente
Mis pensamientos exactamente, y precisamente por qué mientras (1) es más rápido jajaja Aunque creo que fue simplemente el viejo caso de un gerente tratando de convencerse de que él sabe sobre la codificación cuando no lo sabe, todos lo hemos visto, todo el mundo quiere ser un Jedi.
ekerner
8
Absolutamente, esto NO es una exageración en absoluto. Definitivamente, svn-annotate o git blame(o lo que sea) se utilizará aquí, y en general lleva unos minutos cargar un historial de culpa de archivos. Luego, finalmente, para decidir "ah, entiendo, el autor escribió esta línea cuando recién salía de la escuela secundaria", acaba de perder 10 minutos ...
v.oddou
11
Votado porque estoy harto de las convenciones. El desarrollador tiene esta pequeña oportunidad de escribir un número que le gusta, y luego alguien aburrido viene y le pregunta por qué no lo es 1.
Utkan Gezer
1
Votado a favor debido a la retórica. Leer while (1) es exactamente la misma velocidad que while (2).
hdante
44
Pasé por el mismo proceso mental descrito en esta respuesta hace un minuto cuando vi while(2)en el código (hasta considerar "¿Los mantenedores de esta base de código usan while (n) como un oscuro mecanismo de comentarios para hacer que los bucles se vean diferentes?" ) ¡Entonces no, no estás exagerando!
Hisham HM
151

Las respuestas existentes que muestran el código generado por un compilador particular para un objetivo particular con un conjunto particular de opciones no responden completamente la pregunta, a menos que la pregunta se haya hecho en ese contexto específico ("Lo que es más rápido usando gcc 4.7.2 para x86_64 con opciones predeterminadas? ", por ejemplo).

En lo que respecta a la definición del lenguaje, en la máquina abstracta se while (1) evalúa la constante entera 1y se while (2)evalúa la constante entera 2; en ambos casos, el resultado se compara para igualdad a cero. El estándar del lenguaje no dice absolutamente nada sobre el rendimiento relativo de las dos construcciones.

Me imagino que un compilador extremadamente ingenuo podría generar un código de máquina diferente para las dos formas, al menos cuando se compila sin solicitar la optimización.

Por otro lado, los compiladores de C absolutamente deben evaluar algunas expresiones constantes en el momento de la compilación, cuando aparecen en contextos que requieren una expresión constante. Por ejemplo, esto:

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

requiere un diagnóstico; un compilador diferido no tiene la opción de diferir la evaluación 2+2hasta el momento de la ejecución. Dado que un compilador debe tener la capacidad de evaluar expresiones constantes en el momento de la compilación, no hay una buena razón para no aprovechar esa capacidad incluso cuando no es necesaria.

El estándar C ( N1570 6.8.5p4) dice que

Una instrucción de iteración hace que una instrucción llamada cuerpo del bucle se ejecute repetidamente hasta que la expresión de control se compare igual a 0.

Entonces, las expresiones constantes relevantes son 1 == 0y 2 == 0, las cuales se evalúan según el intvalor0 . (Estas comparaciones están implícitas en la semántica del whilebucle; no existen como expresiones C reales).

Un compilador perversamente ingenuo podría generar un código diferente para las dos construcciones. Por ejemplo, para el primero podría generar un bucle infinito incondicional (tratando 1como un caso especial), y para el segundo podría generar una comparación explícita de tiempo de ejecución equivalente a2 != 0 . Pero nunca he encontrado un compilador de C que realmente se comportara de esa manera, y dudo seriamente de que exista ese compilador.

La mayoría de los compiladores (estoy tentado a decir que todos los compiladores con calidad de producción) tienen opciones para solicitar optimizaciones adicionales. Bajo tal opción, es aún menos probable que cualquier compilador genere un código diferente para las dos formas.

Si su compilador genera un código diferente para las dos construcciones, primero verifique si las diferentes secuencias de código realmente tienen un rendimiento diferente. Si lo hacen, intente compilar nuevamente con una opción de optimización (si está disponible). Si aún difieren, envíe un informe de error al proveedor del compilador. No es (necesariamente) un error en el sentido de una falla para cumplir con el estándar C, pero es casi seguro que es un problema que debe corregirse.

En pocas palabras: while (1)y while(2) casi con seguridad tienen el mismo rendimiento. Tienen exactamente la misma semántica, y no hay una buena razón para que ningún compilador genere un código idéntico.

Y aunque es perfectamente legal para un compilador generar un código más rápido para while(1)que para while(2), es igualmente legal para un compilador generar un código más rápido while(1)que para otra aparición while(1)en el mismo programa.

(Hay otra pregunta implícita en la que usted hizo: ¿Cómo lidiar con un entrevistador que insiste en un punto técnico incorrecto? Probablemente sea una buena pregunta para el sitio de Workplace ).

Keith Thompson
fuente
8
"En este caso, las expresiones constantes (implícitas) relevantes son 1! = 0 y 2! = 0, las cuales se evalúan al valor int 1" ... Esto es demasiado complicado e inexacto. El estándar simplemente dice que la expresión de control whiledebe ser de tipo escalar y el cuerpo del bucle se repite hasta que la expresión se compare igual a 0. No dice que haya un implícito expr != 0que se evalúa ... que requeriría el resultado de que - 0 o 1 - a su vez se compara con 0, hasta el infinito. No, la expresión se compara con 0, pero esa comparación no produce un valor. PD: he votado.
Jim Balter
3
@ JimBalter: veo su punto y actualizaré mi respuesta para abordarlo. Sin embargo, lo que quise decir fue que la redacción del estándar "... hasta que la expresión de control se compare igual a 0" implica evaluar <expr> == 0; Eso es lo que "se compara igual a 0" medios en C. Esta comparación es parte de la semántica de la whilebucle. No hay ninguna implicación, ni en el estándar ni en mi respuesta, de que el resultado deba compararse 0nuevamente. (Debería haber escrito en ==lugar de !=). Pero esa parte de mi respuesta no estaba clara, y la actualizaré.
Keith Thompson
1
"" se compara igual a 0 "significa en C", pero ese lenguaje está en el estándar , no en C ... la implementación hace la comparación, no genera una comparación de lenguaje C. Usted escribe "los cuales evalúan al valor int 1", pero nunca se produce dicha evaluación. Si nos fijamos en el código generado para while (2),while (pointer) , while (9.67), por el compilador más ingenua, sin optimizar, no podrá ver ningún código generado que los rendimientos 0o 1. "Debería haber escrito == en lugar de! =" - no, eso no habría tenido ningún sentido.
Jim Balter
2
@ JimBalter: Hmmm. No quiero sugerir que el "compara igual a 0" implica la existencia de una ... == 0expresión C. Mi punto es que tanto el "compara igual a 0" requerido por la descripción de whilebucles del estándar como una x == 0expresión explícita implican lógicamente la misma operación. Y creo que dolorosamente compilador de C ingenuo podría generar código que genere un intvalor de 0o 1para cualquier whilebucle, aunque no creo que ningún compilador real sea tan ingenuo.
Keith Thompson
12
Nota: Esta ya es una pregunta de The Workplace : workplace.stackexchange.com/questions/4314/...
Joe
141

Espera un minuto. El entrevistador, ¿se parecía a este tipo?

ingrese la descripción de la imagen aquí

Ya es bastante malo que el propio entrevistador haya fallado en esta entrevista, ¿qué pasa si otros programadores de esta compañía han "pasado" esta prueba?

No. Evaluar las declaraciones 1 == 0y 2 == 0 debe ser igualmente rápido. Podríamos imaginar implementaciones de compiladores deficientes donde una podría ser más rápida que la otra. Pero no hay una buena razón por la cual uno debería ser más rápido que el otro.

Incluso si hay alguna circunstancia oscura en la que la afirmación sería cierta, los programadores no deberían ser evaluados en base al conocimiento de trivialidades oscuras (y en este caso espeluznantes). No se preocupe por esta entrevista, el mejor movimiento aquí es alejarse.

Descargo de responsabilidad: Esta NO es una caricatura original de Dilbert. Esto es simplemente un mashup .

janos
fuente
66
Esto sería divertido si contuviera también una respuesta a la pregunta.
Cody Gray
no, pero realmente, todos podemos imaginar con bastante facilidad que todos los compiladores escritos por compañías serias producirán un código razonable. tomemos el "caso no optimizado" / O0, tal vez termine como ha publicado anatolyg. Entonces es una cuestión de CPU, ¿se cmpejecutará el operando en menos ciclos comparando 1 a 0 que 2 a 0? ¿Cuántos ciclos se necesitan para ejecutar cmp en general? ¿Es variable según los patrones de bits? ¿son patrones de bits más "complejos" que se ralentizan cmp? No lo se personalmente. podrías imaginar una implementación súper idiota comprobando poco a poco del rango 0 a n (por ejemplo, n = 31).
v.oddou
55
Ese también es mi punto: el cmpoperando debería ser igualmente rápido para 1 y 200. Probablemente podríamos imaginar implementaciones idiotas donde este no sea el caso. Pero ¿podemos imaginar una implementación no idiota donde while(1)sea ​​más rápido que while(200)? Del mismo modo, si en una era prehistórica, la única implementación disponible era así de idiota, ¿deberíamos preocuparnos por eso hoy? No lo creo, esta es una charla de jefe de pelo puntiagudo, ¡y una verdadera joya!
janos
@ v.ouddou "se ejecutará el operando cmp en menos ciclos comparando 1 a 0 que 2 a 0" - No. Debería aprender qué es un ciclo. "No lo sé personalmente. Podrías imaginar una implementación súper idiota comprobando poco a poco desde el rango 0 hasta el n", o de otra manera, aún convirtiendo al entrevistador en un idiota despistado. ¿Y por qué preocuparse por comprobar poco a poco? La implementación podría ser un hombre en una caja que decide tomar un descanso para almorzar en medio de la evaluación de su programa.
Jim Balter
79

Tu explicación es correcta. Esta parece ser una pregunta que pone a prueba su autoconfianza además del conocimiento técnico.

Por cierto, si respondiste

Ambas piezas de código son igualmente rápidas, porque ambas tardan un tiempo infinito en completarse

el entrevistador diría

Pero while (1)puede hacer más iteraciones por segundo; ¿puedes explicar porque? (esto no tiene sentido; prueba tu confianza nuevamente)

Entonces, respondiendo como lo hizo, ahorró algo de tiempo que de lo contrario perdería al discutir esta mala pregunta.


Aquí hay un código de ejemplo generado por el compilador en mi sistema (MS Visual Studio 2012), con las optimizaciones desactivadas:

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

Con optimizaciones activadas:

xxx:
    jmp xxx

Entonces, el código generado es exactamente el mismo, al menos con un compilador de optimización.

anatolyg
fuente
28
Este código es realmente lo que el compilador genera en mi sistema. No lo inventé.
anatolyg
10
icepack "El operando para while es de tipo booleano" - sin sentido. ¿Eres el entrevistador? Le sugiero que se familiarice con el lenguaje C y su estándar antes de hacer tales afirmaciones.
Jim Balter
29
"No lo inventé". - Por favor, no le hagas caso a Icepack, que dice tonterías. C no tiene ningún tipo booleano (sí tiene _Bool en stdbool.h, pero eso no es lo mismo, y la semántica de whilemucho tiempo lo precedió) y el operando de whileno es booleano o _Bool o cualquier otro tipo específico. El operando de whilepuede ser cualquier expresión ... el while se rompe en 0 y continúa en un no-0.
Jim Balter
36
"Ambas piezas de código son igualmente rápidas, porque ambas tardan un tiempo infinito en completarse" me hicieron pensar en algo interesante. Las únicas formas de terminar un bucle infinito sería que una partícula cargada voltee un bit o que el hardware falle: cambiando la declaración de while (00000001) {}a while (00000000) {}. Cuantos más bits verdaderos tenga, menos posibilidades tendrá de que el valor se convierta en falso. Lamentablemente, 2 también solo tiene un bit verdadero. 3, sin embargo, correría por mucho más tiempo. Esto también solo se aplica a un compilador que no siempre optimiza esto (VC ++).
Jonathan Dickinson
8
@ mr5 no. Para que un pequeño cambio realmente resulte en algo como esto, estás hablando de tiempos de ejecución en decenas de miles de años. Simplemente un experimento mental. Si proviene de una raza inmortal, puede usar -1 para evitar que el cambio de bits afecte a su programa.
Jonathan Dickinson
60

La explicación más probable para la pregunta es que el entrevistador cree que el procesador verifica los bits individuales de los números, uno por uno, hasta que alcanza un valor distinto de cero:

1 = 00000001
2 = 00000010

Si el "es cero?" El algoritmo comienza desde el lado derecho del número y tiene que verificar cada bit hasta que alcanza un bit distinto de cero, el while(1) { }bucle tendría que verificar el doble de bits por iteración que el while(2) { }bucle.

Esto requiere un modelo mental muy erróneo de cómo funcionan las computadoras, pero tiene su propia lógica interna. Una forma de verificar sería preguntar si sería while(-1) { }o while(3) { }sería igualmente rápido, o si while(32) { }sería aún más lento .

Ryan Cavanaugh
fuente
39
Supuse que la interpretación errónea del entrevistador sería más como "2 es un int que necesita convertirse a booleano para poder usarse en una expresión condicional, mientras que 1 ya es booleano".
Russell Borogove
77
Y si el algoritmo de comparación comienza desde la izquierda, es al revés.
Paŭlo Ebermann
1
+1, eso es exactamente lo que pensaba. Podrías imaginar perfectamente a alguien creyendo que, fundamentalmente, el cmpalgoritmo de una CPU es una comprobación de bits lineal con salida de bucle temprana en la primera diferencia.
v.oddou
1
@PeterCordes "Nunca he escuchado a nadie (aparte de usted) argumentar que la -O0salida de gcc o clang no es lo suficientemente literal". Nunca dije tal cosa y no tenía en mente gcc o clang. Debes estar malinterpretándome. No creo que lo que produce MSVC sea gracioso.
blubberdiblub
1
@PeterCordes Estaba equivocado, en MSVC un punto de interrupción en el while(1)no se rompe antes del bucle, sino en la expresión. Pero aún se colapsa dentro del bucle cuando se optimiza la expresión. Toma este código con muchos descansos. En el modo de depuración no optimizado, obtienes esto . Al optimizar, muchos puntos de interrupción se unen o incluso se extienden a la siguiente función (el IDE muestra los puntos de interrupción del resultado durante la depuración): el desmontaje correspondiente .
blubberdiblub
32

Por supuesto, no conozco las intenciones reales de este gerente, pero propongo una visión completamente diferente: al contratar a un nuevo miembro en un equipo, es útil saber cómo reacciona ante situaciones de conflicto.

Te condujeron al conflicto. Si esto es cierto, son inteligentes y la pregunta era buena. Para algunas industrias, como la banca, publicar su problema en Stack Overflow podría ser un motivo de rechazo.

Pero, por supuesto, no lo sé, solo propongo una opción.

Tõnu Samuel
fuente
De hecho, es excelente, pero while (2) vs while (1) obviamente está tomado de los cómics de dilbert. NO PUEDE ser inventado por alguien en su sano juicio (¿cómo se le ocurre a alguien (2) como una cosa posible para escribir de todos modos?). Si su hipótesis fuera cierta, definitivamente le daría un problema tan único que puede buscarlo en Google. Como "es while (0xf00b442) más lento que while (1)", ¿de qué otra manera el banco encontraría la pregunta del entrevistado? ¿crees que son la NSA y tienen acceso a keycore?
v.oddou
25

Creo que la pista se encuentra en "preguntado por un gerente superior". Obviamente, esta persona dejó de programar cuando se convirtió en gerente y luego le tomó varios años convertirse en gerente superior. Nunca perdió interés en la programación, pero nunca escribió una línea desde aquellos días. Por lo tanto, su referencia no es "ningún compilador decente" como mencionan algunas respuestas, sino "el compilador con el que esta persona trabajó hace 20-30 años".

En ese momento, los programadores pasaban un porcentaje considerable de su tiempo probando varios métodos para hacer que su código fuera más rápido y más eficiente, ya que el tiempo de CPU del 'minicomputador central' era tan valioso. Al igual que la gente que escribía compiladores. Supongo que el compilador único que su compañía puso a disposición en ese momento optimizó sobre la base de 'declaraciones frecuentes que se pueden optimizar' y tomó un poco de atajo cuando encontró un momento (1) y evaluó todo más, incluido un rato (2). Habiendo tenido tal experiencia podría explicar su posición y su confianza en ella.

El mejor enfoque para que lo contraten es probablemente uno que le permita al gerente senior dejarse llevar y dar una conferencia de 2 a 3 minutos sobre "los buenos viejos tiempos de la programación" antes de que USTED lo guíe suavemente hacia el próximo tema de la entrevista. (El buen momento es importante aquí, demasiado rápido e interrumpe la historia, demasiado lento y se le etiqueta como alguien con un enfoque insuficiente). Dígale al final de la entrevista que estaría muy interesado en aprender más sobre este tema.

OldFrank
fuente
19

Deberías haberle preguntado cómo llegó a esa conclusión. Bajo cualquier compilador decente, los dos compilan con las mismas instrucciones asm. Entonces, él debería haberte dicho también el compilador para comenzar. Y aun así, tendrías que conocer muy bien el compilador y la plataforma para incluso hacer una suposición teórica. Y al final, realmente no importa en la práctica, ya que hay otros factores externos como la fragmentación de la memoria o la carga del sistema que influirán en el ciclo más que este detalle.

Valentin Radu
fuente
14
@GKFX Si ha dado su respuesta y le dicen que está equivocado, no hay razón para que no pueda pedirles que expliquen por qué. Si Anatolyg está en lo correcto y es una prueba de tu confianza en ti mismo, entonces deberías explicar por qué respondiste de la misma manera y preguntarles lo mismo.
P.Turpie
Quise decir lo primero que les dices. No puede ser "¿Por qué x es más rápido?" "No sé; ¿por qué es x más rápido?". Obviamente, habiendo respondido correctamente, puede preguntar.
GKFX
18

En aras de esta pregunta, debo agregar que recuerdo a Doug Gwyn del Comité C escribiendo que algunos de los primeros compiladores de C sin la aprobación del optimizador generarían una prueba en ensamblado para el while(1)(en comparación con el for(;;)que no lo tendría).

Le respondería al entrevistador dándole esta nota histórica y luego le diría que, aunque me sorprendería mucho que algún compilador hiciera esto, un compilador podría tener:

  • sin optimizador pasar el compilador generar una prueba para ambos while(1)ywhile(2)
  • Con el paso del optimizador, el compilador recibe instrucciones de optimizar (con un salto incondicional) todo while(1)porque se consideran idiomáticos. Esto dejaría al while(2)examen y, por lo tanto, hace una diferencia de rendimiento entre los dos.

Por supuesto, agregaría al entrevistador que no considerar while(1)y while(2)el mismo constructo es un signo de optimización de baja calidad, ya que estos son constructos equivalentes.

ouah
fuente
10

Otra versión de esa pregunta sería ver si tienes el coraje de decirle a tu gerente que él / ella está equivocado. Y cuán suavemente puedes comunicarlo.

Mi primer instinto habría sido generar una salida de ensamblaje para mostrarle al administrador que cualquier compilador decente debería encargarse de ello, y si no lo hace, enviará el próximo parche para ello :)

Tío
fuente
8

Para ver a tanta gente profundizar en este problema, muestra exactamente por qué esto podría ser una prueba para ver qué tan rápido desea micro-optimizar las cosas.

Mi respuesta sería; no importa mucho, prefiero centrarme en el problema comercial que estamos resolviendo. Después de todo, eso es lo que me pagarán.

Además, elegiría while(1) {}porque es más común, y otros compañeros de equipo no necesitarían pasar tiempo para descubrir por qué alguien iría por un número mayor que 1.

Ahora ve a escribir un código. ;-)

Bart Verkoeijen
fuente
1
A menos que le paguen para optimizar un código en tiempo real para el que necesita afeitarse solo 1 o 2 milisegundos para adaptarse a sus demandas de tiempo de ejecución. Por supuesto, ese es un trabajo para el optimizador, dirían algunos, es decir, si tiene un optimizador para su arquitectura.
Neowizard
6

Si está tan preocupado por la optimización, debe usar

for (;;)

porque eso no tiene pruebas (modo cínico)

Tom Tanner
fuente
2
Por eso es aconsejable usarlo while(2), deja espacio para la optimización, luego simplemente cambia a for (;;)cuando estés listo para un aumento de rendimiento.
Groo
5

Me parece que esta es una de esas preguntas de entrevista conductual enmascaradas como una pregunta técnica. Algunas compañías hacen esto: harán una pregunta técnica que debería ser bastante fácil de responder para cualquier programador competente, pero cuando el entrevistado dé la respuesta correcta, el entrevistador les dirá que están equivocados.

La compañía quiere ver cómo reaccionarás en esta situación. ¿Te sientas en silencio y no presionas para que tu respuesta sea correcta, ya sea por dudas o por temor a molestar al entrevistador? ¿O estás dispuesto a desafiar a una persona con autoridad que sabes que está mal? Quieren ver si está dispuesto a defender sus convicciones, y si puede hacerlo con tacto y respeto.

pacoverflow
fuente
3

Solía ​​programar el código C y ensamblado cuando este tipo de tonterías podrían haber hecho una diferencia. Cuando marcó la diferencia, lo escribimos en la Asamblea.

Si me hicieran esa pregunta, habría repetido la famosa cita de 1974 de Donald Knuth sobre la optimización prematura y habría caminado si el entrevistador no se reía y continuaba.

Peter Wooster
fuente
1
Dado que también dijo "En las disciplinas de ingeniería establecidas, una mejora del 12%, obtenida fácilmente, nunca se considera marginal y creo que el mismo punto de vista debería prevalecer en la ingeniería de software", creo que caminar no está justificado.
Alice
3

Tal vez el entrevistador hizo una pregunta tan tonta intencionalmente y quería que hicieras 3 puntos:

  1. Razonamiento básico Ambos bucles son infinitos, es difícil hablar de rendimiento.
  2. Conocimiento sobre niveles de optimización. Quería saber de usted si deja que el compilador haga alguna optimización por usted, optimizaría la condición, especialmente si el bloque no estaba vacío.
  3. Conocimiento sobre arquitectura de microprocesador. La mayoría de las arquitecturas tienen una instrucción especial de CPU para comparar con 0 (aunque no necesariamente más rápido).
Rok Kralj
fuente
2

Aquí hay un problema: si realmente escribe un programa y mide su velocidad, ¡la velocidad de ambos bucles podría ser diferente! Para alguna comparación razonable:

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

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

con un código agregado que imprime el tiempo, algún efecto aleatorio como la forma en que se coloca el bucle dentro de una o dos líneas de caché podría marcar la diferencia. Por pura casualidad, un bucle podría estar completamente dentro de una línea de caché, o al comienzo de una línea de caché, o podría abarcar dos líneas de caché. Y como resultado, cualquier cosa que el entrevistador afirme que es más rápida podría ser realmente más rápida, por coincidencia.

En el peor de los casos: un compilador de optimización no se da cuenta de lo que hace el bucle, pero se da cuenta de que los valores producidos cuando se ejecuta el segundo bucle son los mismos que los producidos por el primero. Y generar código completo para el primer bucle, pero no para el segundo.

gnasher729
fuente
1
El razonamiento de "líneas de caché" solo funcionará en un chip que tenga un caché extremadamente pequeño.
sharptooth
10
Esto no viene al caso. Las preguntas sobre velocidad suponen "todo lo demás igual".
Jim Balter
66
@ JimBalter: No se trataba de una cuestión de velocidad, se trataba de una discusión con un entrevistador. Y la posibilidad de que hacer una prueba real pueda probar que el entrevistador es "correcto", por razones que no tienen nada que ver con su argumento, sino que son pura coincidencia. Lo que te pondría en una situación embarazosa si trataras de demostrar que estaba equivocado.
gnasher729
1
@ gnasher729 Dejando de lado todos los argumentos lógicos, valdría la pena analizar el caso de coincidencia del que hablas. Pero creer ciegamente que tal caso existe no solo es ingenuo sino estúpido. Mientras no expliques qué, cómo o por qué sucedería eso, esta respuesta es inútil.
user568109
44
@ gnasher729 "Esta no era una cuestión de velocidad" - No debatiré con las personas que emplean técnicas retóricas deshonestas.
Jim Balter
2

Ambos son iguales, lo mismo.

Según las especificaciones, todo lo que no sea 0 se considera verdadero, por lo que incluso sin ninguna optimización, y un buen compilador no generará ningún código para while (1) o while (2). El compilador generaría una verificación simple para != 0.

Nikolay Ivanchev
fuente
@djechlin, porque ambos toman 1 ciclo de CPU.
UncleKing
La constante del compilador lo dobla, por lo que ni siquiera se evalúa en tiempo de ejecución.
PaulHK
2

A juzgar por la cantidad de tiempo y esfuerzo que la gente ha pasado probando, probando y respondiendo a esta pregunta directa, diría que ambos se hicieron muy lentos al hacer la pregunta.

Y así pasar más tiempo en ello ...

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

"while (1)" y "while (true)" se usan históricamente para hacer un ciclo infinito que espera que se llame "break" en algún momento dentro del ciclo en función de una condición que ciertamente ocurrirá.

El "1" simplemente está ahí para evaluar siempre como verdadero y, por lo tanto, decir "while (2)" es tan tonto como decir "while (1 + 1 == 2)" que también evaluará como verdadero.

Y si quieres ser completamente tonto solo usa: -

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

Me gustaría pensar que su codificador cometió un error tipográfico que no afectó la ejecución del código, pero si intencionalmente usó el "2" solo para ser extraño, despídalo antes de que ponga todo lo extraño en su código, lo que hace difícil leer y trabajar con

ekerner
fuente
La reversión que hizo revierte la edición por un usuario de muy alta reputación. Estas personas generalmente conocen muy bien las políticas y están aquí para enseñárselos. Creo que la última línea de tu publicación no agrega nada útil a tu publicación. Incluso si recibiera el chiste que obviamente me estoy perdiendo, lo consideraría demasiado hablador, no vale la pena el párrafo adicional.
Palec
@Palec: Gracias por el comentario. Descubrí que el mismo tipo editó muchas de mis publicaciones, principalmente solo para eliminar el cierre de sesión. Entonces pensé que era solo un troll de reputación, y de ahí su reputación. ¿Los foros en línea han agotado tanto el lenguaje, y las cortesías comunes, que ya no firmamos nuestros escritos? tal vez soy un poco de la vieja escuela, pero parece grosero omitir saludos y despedidas. Estamos usando aplicaciones, pero seguimos siendo humanos.
ekerner
1
En Stack Exchange , saludos, lemas, agradecimientos, etc. no son bienvenidos . Lo mismo se menciona en el centro de ayuda , específicamente bajo el comportamiento esperado . Ninguna parte de Stack Exchange , ni siquiera Stack Overflow , es un foro, son sitios de preguntas y respuestas. La edición es una de las diferencias. Además, los comentarios deben servir solo para proporcionar comentarios sobre la publicación, no para discusión ( Stack Overflow Chat ). Y hay muchas otras formas en que las preguntas y respuestas difieren de un foro. Más sobre eso en el centro de ayuda y sobre Meta Stack Overflow .
Palec
Tenga en cuenta que cuando tiene más de 2000 repeticiones (privilegio de edición), no obtiene más repeticiones de las ediciones. En caso de duda, se ha aplicado a su publicación por qué y no está de acuerdo con la edición, o si ve el mal comportamiento de alguien, pregunte en Meta Stack Overflow . Si el editor hizo lo incorrecto, podría ser notificado por un mod (tal vez no se dieron cuenta) o incluso recibir un castigo de alguna manera (si el comportamiento fue intencionalmente malicioso). De lo contrario, obtendrá punteros para explicar por qué la edición / comportamiento es correcto. La reversión innecesaria desordena el historial de revisiones y puede meterte en una guerra de reversión. Retrocedo solo cuando estoy realmente seguro de que la edición es incorrecta.
Palec
Finalmente sobre la firma, el saludo, ...: Puede parecer grosero no incluir estos trámites, pero aumenta la relación señal / ruido y no se pierde información. Puedes elegir el apodo que quieras, esa es tu firma. En la mayoría de los lugares también tienes tu avatar.
Palec
1

Eso depende del compilador.

Si optimiza el código, o si evalúa 1 y 2 como verdadero con el mismo número de instrucciones para un conjunto de instrucciones en particular, la velocidad de ejecución será la misma.

En casos reales, siempre será igual de rápido, pero sería posible imaginar un compilador particular y un sistema particular cuando esto se evaluaría de manera diferente.

Quiero decir: esta no es realmente una pregunta relacionada con el lenguaje (C).

usuario143522
fuente
0

Dado que las personas que buscan responder a esta pregunta quieren el ciclo más rápido, habría respondido que ambos están compilando igualmente en el mismo código de ensamblaje, como se indica en las otras respuestas. Sin embargo, puede sugerirle al entrevistador que use 'desenrollar bucle'; a do {} while loop en lugar del while while.

Cauteloso: debe asegurarse de que el ciclo al menos siempre se ejecute una vez .

El bucle debe tener una condición de ruptura en el interior.

También para ese tipo de bucle preferiría personalmente el uso de do {} while (42) ya que cualquier número entero, excepto 0, haría el trabajo.

Antonin GAVREL
fuente
¿Por qué sugeriría un bucle do {} while? Mientras que (1) (o mientras que (2)) hace lo mismo.
Catsunami el
hacer mientras elimina un salto extra para un mejor rendimiento. Por supuesto, en el caso de que sepa que el bucle se ejecutará al menos una vez
Antonin GAVREL el
0

La respuesta obvia es: según lo publicado, ambos fragmentos ejecutarían un bucle infinito igualmente ocupado, lo que hace que el programa sea infinitamente lento .

Aunque redefinir las palabras clave C como macros técnicamente tendría un comportamiento indefinido, es la única forma en que puedo pensar para hacer que cualquiera de los fragmentos de código sea rápido: puede agregar esta línea sobre los 2 fragmentos:

#define while(x) sleep(x);

de hecho hará el while(1)doble de rápido (o la mitad de lento) que while(2).

chqrlie
fuente
@MaxB: de hecho, el truco macro debe estar aún más retorcido. Creo que la nueva versión debería funcionar, aunque no está garantizada por el estándar C.
chqrlie
-4

La única razón por la que puedo pensar por qué while(2)sería más lento es:

  1. El código optimiza el ciclo para

    cmp eax, 2

  2. Cuando ocurre la resta, estás esencialmente restando

    a. 00000000 - 00000010 cmp eax, 2

    en vez de

    si. 00000000 - 00000001 cmp eax, 1

cmpsolo establece banderas y no establece un resultado. Entonces, en los bits menos significativos, sabemos si necesitamos pedir prestado o no con b . Mientras que con un tiene que realizar dos restas antes de obtener un préstamo.

hdost
fuente
15
cmp tomará 1 ciclo de CPU independientemente en ambos casos.
UncleKing
8
Ese código sería incorrecto. El código correcto cargaría 2 o 1 en el registro eax, luego compararía eax con 0.
gnasher729