Hay un comentario en la biblioteca de compresión zlib (que se usa en el proyecto Chromium entre muchos otros) que implica que un bucle do-while en C genera un código "mejor" en la mayoría de los compiladores. Aquí está el fragmento de código donde aparece.
do {
} while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
scan < strend);
/* The funny "do {}" generates better code on most compilers */
https://code.google.com/p/chromium/codesearch#chromium/src/third_party/zlib/deflate.c&l=1225
¿Existe alguna evidencia de que la mayoría (o alguno) de los compiladores generaría un código mejor (por ejemplo, más eficiente)?
Actualización: Mark Adler , uno de los autores originales, dio un poco de contexto en los comentarios.
c
performance
compiler-construction
Dennis
fuente
fuente
The funny "do {}" generates better code
--- ¿mejor que qué? Que divertido mientras () o que aburrido, ¿regular {}?do
, a diferencia de solo awhile
, no evita una rama condicional.Respuestas:
Ante todo:
Un
do-while
bucle no es lo mismo que unwhile
bucle o unfor
bucle.while
y losfor
bucles pueden no ejecutar el cuerpo del bucle en absoluto.do-while
bucle siempre ejecuta el cuerpo del bucle al menos una vez; omite la verificación de condición inicial.Entonces esa es la diferencia lógica. Dicho esto, no todos se adhieren estrictamente a esto. Es bastante común que se utilicen bucles for
while
ofor
incluso cuando se garantiza que siempre se repetirá al menos una vez. (Especialmente en idiomas con bucles foreach ).Entonces, para evitar comparar manzanas y naranjas, procederé asumiendo que el ciclo siempre se ejecutará al menos una vez. Además, no volveré a mencionar los
for
bucles, ya que son esencialmentewhile
bucles con un poco de azúcar de sintaxis para un contador de bucles.Entonces estaré respondiendo a la pregunta:
Si
while
se garantiza que un bucle se repita al menos una vez, ¿hay alguna ganancia de rendimiento al usar undo-while
bucle en su lugar?A
do-while
omite la primera comprobación de condición. Entonces, hay una rama menos y una condición menos para evaluar.Si la condición es costosa de verificar y sabe que tiene la garantía de realizar un bucle al menos una vez, entonces un
do-while
bucle podría ser más rápido.Y aunque esto se considera una microoptimización en el mejor de los casos, es una que el compilador no siempre puede hacer: específicamente cuando el compilador no puede probar que el bucle siempre entrará al menos una vez.
En otras palabras, un ciclo while:
Es efectivamente lo mismo que esto:
Si sabe que siempre hará un bucle al menos una vez, esa instrucción if es extraña.
Del mismo modo, a nivel de ensamblaje, así es aproximadamente como se compilan los diferentes bucles para:
bucle de hacer mientras:
bucle while:
Tenga en cuenta que la condición se ha duplicado. Un enfoque alternativo es:
... que cambia el código duplicado por un salto adicional.
De cualquier manera, sigue siendo peor que un
do-while
bucle normal .Dicho esto, los compiladores pueden hacer lo que quieran. Y si pueden demostrar que el bucle siempre entra una vez, entonces ha hecho el trabajo por usted.
Pero las cosas son un poco extrañas para el ejemplo particular de la pregunta porque tiene un cuerpo de bucle vacío. Como no hay cuerpo, no hay diferencia lógica entre
while
ydo-while
.FWIW, probé esto en Visual Studio 2012:
Con el cuerpo vacío, en realidad genera el mismo código para
while
ydo-while
. Así que esa parte probablemente sea un remanente de los viejos tiempos cuando los compiladores no eran tan buenos.Pero con un cuerpo no vacío, VS2012 logra evitar la duplicación del código de condición, pero aún genera un salto condicional adicional.
Así que es irónico que, si bien el ejemplo de la pregunta resalta por qué un
do-while
bucle podría ser más rápido en el caso general, el ejemplo en sí no parece ofrecer ningún beneficio en un compilador moderno.Teniendo en cuenta la antigüedad del comentario, solo podemos adivinar por qué sería importante. Es muy posible que los compiladores en ese momento no fueran capaces de reconocer que el cuerpo estaba vacío. (O si lo hicieron, no usaron la información).
fuente
do-while
ciclo más rápido que un ciclo while. Y respondí la pregunta diciendo que puede ser más rápido. No dije cuánto. No dije si valía la pena. No le recomendé a nadie que comenzara a convertir para bucles do-while. Pero simplemente negar que existe la posibilidad de una optimización, incluso si es pequeña, es, en mi opinión, un flaco favor para quienes sí se preocupan y están interesados en estas cosas.No mucho, a menos que observe el ensamblaje generado real de un compilador específico real en una plataforma específica con algunas configuraciones de optimización específicas.
Probablemente valió la pena preocuparse por esto hace décadas (cuando se escribió ZLib), pero ciertamente no hoy en día, a menos que descubra, mediante un perfil real, que esto elimina un cuello de botella de su código.
fuente
premature optimization
viene a la mente aquí.En pocas palabras (tl; dr):
Estoy interpretando el comentario en el código de los OP de manera un poco diferente, creo que el "mejor código" que afirman haber observado se debió a mover el trabajo real a la "condición" del ciclo. Sin embargo, estoy completamente de acuerdo en que es muy específico del compilador y que la comparación que hicieron, aunque pueden producir un código ligeramente diferente, es en su mayoría inútil y probablemente obsoleta, como muestro a continuación.
Detalles:
Es difícil decir qué quiso decir el autor original con su comentario sobre la
do {} while
producción de un mejor código, pero me gustaría especular en otra dirección que la que se planteó aquí; creemos que la diferencia entre los buclesdo {} while
ywhile {}
es bastante pequeña (una rama menos como Mystical dijo), pero hay algo aún más "divertido" en este código y es poner todo el trabajo dentro de esta loca condición y mantener la parte interna vacía (do {}
).Probé el siguiente código en gcc 4.8.1 (-O3), y da una diferencia interesante:
Después de compilar -
Entonces, el primer ciclo hace 7 instrucciones mientras que el segundo hace 6, aunque se supone que deben hacer el mismo trabajo. Ahora, realmente no puedo decir si hay algo de inteligencia del compilador detrás de esto, probablemente no y es solo una coincidencia, pero no he comprobado cómo interactúa con otras opciones del compilador que este proyecto podría estar usando.
En clang 3.3 (-O3), por otro lado, ambos bucles generan este código de 5 instrucciones:
Lo que demuestra que los compiladores son bastante diferentes y que avanzan a un ritmo mucho más rápido de lo que algunos programadores podrían haber anticipado hace varios años. También significa que este comentario carece de sentido y probablemente esté ahí porque nadie había comprobado si todavía tiene sentido.
En pocas palabras: si desea optimizar al mejor código posible (y sabe cómo debería verse), hágalo directamente en ensamblador y elimine el "intermediario" (compilador) de la ecuación, pero tenga en cuenta que el más nuevo Los compiladores y HW más nuevos pueden hacer que esta optimización sea obsoleta. En la mayoría de los casos, es mucho mejor dejar que el compilador haga ese nivel de trabajo por usted y concentrarse en optimizar las cosas importantes.
Otro punto que se debe hacer, el recuento de instrucciones (asumiendo que esto es lo que buscaba el código original de los OP), no es de ninguna manera una buena medida para la eficiencia del código. No todas las instrucciones fueron creadas de la misma manera, y algunas de ellas (movimientos simples de registro a registro, por ejemplo) son realmente baratas ya que son optimizadas por la CPU. Otra optimización podría dañar las optimizaciones internas de la CPU, por lo que eventualmente solo cuenta la evaluación comparativa adecuada.
fuente
mov %rcx,%rsi
:) Puedo ver cómo reorganizar el código puede hacer eso.Un
while
bucle a menudo se compila como undo-while
bucle con una rama inicial a la condición, es decirmientras que la compilación de un
do-while
bucle es la misma sin la rama inicial. Puede ver en eso quewhile()
es inherentemente menos eficiente por el costo de la sucursal inicial, que sin embargo solo se paga una vez. [Compare con la forma ingenua de implementaciónwhile,
que requiere tanto una rama condicional como una rama incondicional por iteración].Dicho esto, no son alternativas realmente comparables. Es doloroso transformar un
while
bucle en undo-while
bucle y viceversa. Hacen cosas diferentes. Y en este caso, las varias llamadas a métodos dominarían totalmente cualquier cosa que hiciera el compiladorwhile
frente ado-while.
fuente
La observación no se trata de la elección de la declaración de control (hacer frente a while), ¡se trata de desenrollar el ciclo!
Como puede ver, esta es una función de comparación de cadenas (los elementos de cadena probablemente tienen 2 bytes de longitud), que podría haberse escrito con una sola comparación en lugar de cuatro en un atajo y expresión.
Esta última implementación es sin duda más rápida, ya que realiza una única verificación de la condición de fin de cadena después de cada cuatro comparaciones de elementos, mientras que la codificación estándar implicaría una verificación por comparación. Dicho de otra manera, 5 pruebas por 4 elementos frente a 8 pruebas por 4 elementos.
De todos modos, funcionará solo si la longitud de la cadena es un múltiplo de cuatro o tiene un elemento centinela (de modo que se garantiza que las dos cadenas diferirán más allá del
strend
borde). ¡Bastante arriesgado!fuente
Esta discusión sobre la eficiencia while vs. do es completamente inútil en este caso, ya que no hay cuerpo.
y
son absolutamente equivalentes.
fuente