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 while
finalmente debería evaluar a true
o false
. En este caso, ambos evalúan true
y no hay instrucciones condicionales adicionales dentro de la while
condició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?
c
performance
while-loop
Nikole
fuente
fuente
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.Respuestas:
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:
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=intel
con un indicador de optimización):Con
-O0
:Con
-O1
:Con
-O2
y-O3
(misma salida):De hecho, el ensamblaje generado para el bucle es idéntico para cada nivel de optimización:
Los bits importantes son:
No puedo leer muy bien el ensamblaje, pero obviamente este es un ciclo incondicional. La
jmp
instrucció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 ++:Editar:
Curiosamente, incluso sin optimizaciones , los siguientes bucles produjeron exactamente el mismo resultado (incondicional
jmp
) en el ensamblaje:E incluso para mi asombro:
Las cosas se ponen un poco más interesantes con las funciones definidas por el usuario:
En
-O0
, estos dos ejemplos realmente llamanx
y realizan una comparación para cada iteración.Primer ejemplo (devolviendo 1):
Segundo ejemplo (regresando
sqrt(7)
):Sin embargo, en
-O1
y arriba, ambos producen el mismo ensamblaje que los ejemplos anteriores (unjmp
regreso 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:
fuente
-O
niveles.jmp
volverán al principio o eliminarán el bucle por completo.break
declaración), pero lo probé de todos modos y no, incluso con el código dentro del bucle, el salto es absoluta e incondicional para amboswhile(1)
ywhile(2)
. Siéntase libre de probar a los demás si está realmente preocupado.Sí,
while(1)
es mucho más rápido quewhile(2)
, para un ser humano a leer! Si veowhile(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 usanwhile(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 leerwhile(w)
owhile(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!
fuente
svn-annotate
ogit 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 ...1
.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!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 entera1
y sewhile (2)
evalúa la constante entera2
; 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:
requiere un diagnóstico; un compilador diferido no tiene la opción de diferir la evaluación
2+2
hasta 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
Entonces, las expresiones constantes relevantes son
1 == 0
y2 == 0
, las cuales se evalúan según elint
valor0
. (Estas comparaciones están implícitas en la semántica delwhile
bucle; 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
1
como 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)
ywhile(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 parawhile(2)
, es igualmente legal para un compilador generar un código más rápidowhile(1)
que para otra apariciónwhile(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 ).
fuente
while
debe 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ícitoexpr != 0
que 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.<expr> == 0
; Eso es lo que "se compara igual a 0" medios en C. Esta comparación es parte de la semántica de lawhile
bucle. No hay ninguna implicación, ni en el estándar ni en mi respuesta, de que el resultado deba compararse0
nuevamente. (Debería haber escrito en==
lugar de!=
). Pero esa parte de mi respuesta no estaba clara, y la actualizaré.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 rendimientos0
o1
. "Debería haber escrito == en lugar de! =" - no, eso no habría tenido ningún sentido.... == 0
expresión C. Mi punto es que tanto el "compara igual a 0" requerido por la descripción dewhile
bucles del estándar como unax == 0
expresió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 unint
valor de0
o1
para cualquierwhile
bucle, aunque no creo que ningún compilador real sea tan ingenuo.Espera un minuto. El entrevistador, ¿se parecía a este tipo?
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 == 0
y2 == 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 .
fuente
cmp
ejecutará 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 ralentizancmp
? 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).cmp
operando 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 dondewhile(1)
sea más rápido quewhile(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!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
el entrevistador diría
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:
Con optimizaciones activadas:
Entonces, el código generado es exactamente el mismo, al menos con un compilador de optimización.
fuente
while
mucho tiempo lo precedió) y el operando dewhile
no es booleano o _Bool o cualquier otro tipo específico. El operando dewhile
puede ser cualquier expresión ... el while se rompe en 0 y continúa en un no-0.while (00000001) {}
awhile (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 ++).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:
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 elwhile(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) { }
owhile(3) { }
sería igualmente rápido, o siwhile(32) { }
sería aún más lento .fuente
cmp
algoritmo de una CPU es una comprobación de bits lineal con salida de bucle temprana en la primera diferencia.-O0
salida 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.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 .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.
fuente
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.
fuente
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.
fuente
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 elfor(;;)
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:
while(1)
ywhile(2)
while(1)
porque se consideran idiomáticos. Esto dejaría alwhile(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)
ywhile(2)
el mismo constructo es un signo de optimización de baja calidad, ya que estos son constructos equivalentes.fuente
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 :)
fuente
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. ;-)
fuente
Si está tan preocupado por la optimización, debe usar
porque eso no tiene pruebas (modo cínico)
fuente
while(2)
, deja espacio para la optimización, luego simplemente cambia afor (;;)
cuando estés listo para un aumento de rendimiento.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.
fuente
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.
fuente
Tal vez el entrevistador hizo una pregunta tan tonta intencionalmente y quería que hicieras 3 puntos:
fuente
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:
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.
fuente
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
.fuente
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: -
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
fuente
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).
fuente
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.
fuente
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:
de hecho hará el
while(1)
doble de rápido (o la mitad de lento) quewhile(2)
.fuente
La única razón por la que puedo pensar por qué
while(2)
sería más lento es:El código optimiza el ciclo para
cmp eax, 2
Cuando ocurre la resta, estás esencialmente restando
a.
00000000 - 00000010 cmp eax, 2
en vez de
si.
00000000 - 00000001 cmp eax, 1
cmp
solo 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.fuente