Tengo esta idea de esta pregunta en stackoverflow.com
El siguiente patrón es común:
final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
//...do something
}
El punto que estoy tratando de hacer es que la declaración condicional es algo complicado y no cambia.
¿Es mejor declararlo en la sección de inicialización del ciclo, como tal?
final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
//...do something
}
¿Está esto más claro?
¿Qué pasa si la expresión condicional es simple como
final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
//...do something
}
java
c++
coding-style
language-agnostic
Celeritas
fuente
fuente
x
es grande en magnitud,Math.floor(Math.sqrt(x))+1
es igual aMath.floor(Math.sqrt(x))
. :-){ x=whatever; for (...) {...} }
o, mejor aún, considere si hay suficiente actividad que necesita ser una función separada.Respuestas:
Lo que haría es algo como esto:
Honestamente, la única buena razón para meter la inicialización
j
(ahoralimit
) en el encabezado del bucle es mantenerlo con el alcance correcto. Todo lo que se necesita para hacer que no sea un problema es un buen alcance cerrado.Puedo apreciar el deseo de ser rápido pero no sacrificar la legibilidad sin una buena razón.
Claro, el compilador puede optimizar, inicializar varios vars puede ser legal, pero los bucles son lo suficientemente difíciles de depurar como es. Por favor, sé amable con los humanos. Si esto realmente nos está frenando, es bueno entenderlo lo suficiente como para solucionarlo.
fuente
for(int i = 0; i < n*n; i++){...}
¿no le asignarían*n
una variable, verdad?final
). ¿A quién le importa si una constante que tiene la aplicación del compilador que evita que cambie sea accesible más adelante en la función?Un buen compilador generará el mismo código de cualquier manera, por lo que si va por el rendimiento, solo haga un cambio si está en un ciclo crítico y realmente lo ha perfilado y descubrió que hace la diferencia. Incluso si el compilador no puede optimizarlo, como la gente ha señalado en los comentarios sobre el caso de las llamadas a funciones, en la gran mayoría de las situaciones, la diferencia de rendimiento será demasiado pequeña para que valga la pena la consideración de un programador.
Sin embargo...
No debemos olvidar que el código es principalmente un medio de comunicación entre humanos, y ambas opciones no se comunican muy bien con otros humanos. El primero da la impresión de que la expresión debe calcularse en cada iteración, y el segundo en la sección de inicialización implica que se actualizará en algún lugar dentro del ciclo, donde es realmente constante en todo momento.
En realidad, preferiría que se extrajera por encima del bucle y se hiciera
final
para que quede claro de forma inmediata y abundante para cualquiera que lea el código. Eso tampoco es ideal porque aumenta el alcance de la variable, pero su función de cierre no debe contener mucho más que ese ciclo de todos modos.fuente
Como dijo @Karl Bielefeldt en su respuesta, esto generalmente no es un problema.
Sin embargo, en un momento fue un problema común en C y C ++, y surgió un truco para evitar el problema sin reducir la legibilidad del código: iterar hacia atrás, hacia abajo
0
.Ahora, el condicional en cada iteración es el
>= 0
que cada compilador compilará en 1 o 2 instrucciones de ensamblaje. Cada CPU fabricada en las últimas décadas debería tener controles básicos como estos; haciendo una verificación rápida en mi máquina x64, veo que esto se convierte previsiblemente encmpl $0x0, -0x14(%rbp)
(valor de comparación larga int 0 vs. registro rbp compensado -14) yjl 0x100000f59
(salte a las instrucciones que siguen al ciclo si la comparación anterior era verdadera para "2nd-arg <1er argumento ") .Tenga en cuenta que quité el
+ 1
deMath.floor(Math.sqrt(x)) + 1
; para que las matemáticas funcionen, el valor inicial debe serint i = «iterationCount» - 1
. También vale la pena señalar que su iterador debe estar firmado;unsigned int
no funcionará y probablemente avisará al compilador.Después de programar en lenguajes basados en C durante ~ 20 años, ahora solo escribo bucles de iteración de índice inverso a menos que haya una razón específica para iterar de índice directo. Además de verificaciones más simples en los condicionales, la iteración inversa a menudo también deja de lado lo que de otro modo serían problemáticas mutaciones de matriz mientras itera.
fuente
unsigned
contadores funcionarán aquí si modifica el cheque (la forma más fácil es agregar el mismo valor a ambos lados); por ejemplo, para cualquier decrementoDec
, la verificación(i + Dec) >= Dec
siempre debe tener el mismo resultado que lasigned
verificacióni >= 0
, con ambossigned
yunsigned
contadores, siempre que el lenguaje tenga reglas envolventes bien definidas para lasunsigned
variables (específicamente,-n + n == 0
debe ser cierto para ambassigned
yunsigned
). Sin embargo, tenga en cuenta que esto puede ser menos eficiente que una>=0
verificación firmada , si el compilador no tiene una optimización para ello.Dec
constante tanto al valor inicial como al valor final funciona, pero lo hace mucho menos intuitivo, y si se usai
como un índice de matriz, entonces también necesitaría hacer ununsigned int arrayI = i - Dec;
en el cuerpo del bucle. Solo uso la iteración hacia adelante cuando estoy atascado con un iterador sin signo; a menudo con uni <= count - 1
condicional para mantener la lógica paralela a los bucles de iteración inversa.Dec
específicamente a los valores iniciales y finales, sino a cambiar el control de condiciónDec
en ambos lados.for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/
Esto le permite usari
normalmente dentro del ciclo, al tiempo que garantiza que el valor más bajo posible en el lado izquierdo de la condición es0
(para evitar que interfiera la envoltura). Sin embargo, definitivamente es mucho más simple usar la iteración hacia adelante cuando se trabaja con contadores sin firmar.Se vuelve interesante una vez que Math.sqrt (x) se reemplaza por Mymodule.SomeNonPureMethodWithSideEffects (x).
Básicamente mi modus operandi es: si se espera que algo siempre dé el mismo valor, solo evalúelo una vez. Por ejemplo, List.Count, si se supone que la lista no cambia durante la operación del ciclo, entonces obtenga el conteo fuera del ciclo en otra variable.
Algunos de estos "recuentos" pueden ser sorprendentemente caros, especialmente cuando se trata de bases de datos. Incluso si está trabajando en un conjunto de datos que no se supone que cambie durante la iteración de la lista.
fuente
for( auto it = begin(dataset); !at_end(it); ++it )
En mi opinión, esto es muy específico del idioma. Por ejemplo, si usa C ++ 11, sospecharía que si la verificación de condición fuera una
constexpr
función, es muy probable que el compilador optimice las ejecuciones múltiples, ya que sabe que producirá el mismo valor cada vez.Sin embargo, si la llamada a la función es una función de biblioteca que no es
constexpr
el compilador, es casi seguro que la ejecutará en cada iteración, ya que no puede deducir esto (a menos que esté en línea y, por lo tanto, pueda deducirse como puro).Sé menos sobre Java, pero dado que está compilado JIT, supongo que el compilador tiene suficiente información en tiempo de ejecución para probablemente en línea y optimizar la condición. Pero esto dependería de un buen diseño del compilador y el compilador que decidiera que este bucle era una prioridad de optimización que solo podemos suponer.
En lo personal creo que es un poco más elegante para poner la condición dentro del bucle, si se puede, pero si es complejo voy a escribir en una
constexpr
oinline
función, o sus lenguas equivalant dar a entender que la función es puro y optimisable. Esto hace que la intención sea obvia y mantiene el estilo de bucle idiomático sin crear una gran línea ilegible. También le da un nombre a la condición de verificación si es su propia función para que los lectores puedan ver inmediatamente de forma lógica para qué sirve la verificación sin leerla si es compleja.fuente