java - una - ¿Declarar un objeto dentro o fuera de un bucle?
crear varios objetos con un for java (16)
El primero tiene mucho más sentido. Mantiene la variable en el ámbito en el que se usa. Y evita que los valores asignados en una iteración se usen en una iteración posterior, esto es más defensivo.
A veces se dice que el primero es más eficiente, pero cualquier compilador razonable debería poder optimizarlo para que sea exactamente el mismo que este último.
¿Hay alguna penalización de rendimiento para el siguiente fragmento de código?
for (int i=0; i<someValue; i++)
{
Object o = someList.get(i);
o.doSomething;
}
¿O este código tiene más sentido?
Object o;
for (int i=0; i<someValue; i++)
{
o = someList.get(i);
o.doSomething;
}
Si en código de bytes estos dos son totalmente equivalentes, obviamente el primer método se ve mejor en términos de estilo, pero quiero asegurarme de que este sea el caso.
En ambos casos, la información de tipo para el objeto o se determina en tiempo de compilación. En la segunda instancia, o se ve como global para el ciclo for y, en la primera instancia, el compilador inteligente de Java sabe que o tendrá que estar disponible para mientras dure el bucle y, por lo tanto, optimizará el código de tal forma que no habrá ninguna reincidencia del tipo o en cada iteración. Por lo tanto, en ambos casos, la especificación del tipo de o se hará una vez, lo que significa que la única diferencia de rendimiento estaría en el alcance de o. Obviamente, un alcance más estrecho siempre mejora el rendimiento, por lo tanto, para responder a su pregunta: no, no hay una penalización de rendimiento para el primer recorte de código; en realidad, este recorte de código está más optimizado que el segundo.
En el segundo recorte, se le está dando un alcance innecesario que, además de ser un problema de rendimiento, también puede ser un problema de seguridad.
En los compiladores de hoy, no. Declaro objetos en el alcance más pequeño que puedo, porque es mucho más legible para el siguiente tipo.
Estos tienen una semántica diferente. ¿Cuál es más significativo?
Reutilizar un objeto por "razones de rendimiento" a menudo es incorrecto.
La pregunta es: ¿qué significa el objeto? ¿Por qué lo estás creando? ¿Que representa? Los objetos deben estar en paralelo con las cosas del mundo real. Las cosas se crean, se someten a cambios de estado e informan sus estados por razones.
¿Cuáles son esas razones? ¿Cómo se modela su objeto y reflejan esas razones?
No hay penalización de rendimiento por declarar el Objeto o dentro del ciclo. El compilador genera bytecode muy similar y realiza las optimizaciones correctas.
Ver el artículo Mito: la definición de variables de bucle dentro del bucle es mala para el rendimiento en un ejemplo similar.
No optimice prematuramente. Mejor que cualquiera de estos es:
for(Object o : someList) {
o.doSomething();
}
porque elimina la repetición y aclara la intención.
A menos que esté trabajando en sistemas integrados, en cuyo caso todas las apuestas están desactivadas. De lo contrario, no intentes burlar a la JVM.
Puede desensamblar el código con javap -c y verificar lo que el compilador realmente emite. En mi configuración (java 1.5 / mac compilado con eclipse), el bytecode para el ciclo es idéntico.
Siempre he pensado que la mayoría de los compiladores en estos días son lo suficientemente inteligentes como para hacer la última opción. Suponiendo que ese sea el caso, diría que el primero también se ve mejor. Si el ciclo es muy grande, no hay necesidad de buscar por todos lados donde se declara o.
Tengo que admitir que no conozco a Java. ¿Pero son estos dos equivalentes? ¿Son las vidas del objeto iguales? En el primer ejemplo, supongo (sin saber java) que o será elegible para la recolección de basura inmediatamente después de que termine el ciclo.
Pero en el segundo ejemplo seguramente ¿no será elegible para la recolección de basura hasta que se cierre el alcance externo (no se muestra)?
Como alguien que mantiene más código que escribe código.
La versión 1 es muy preferida: mantener el alcance lo más local posible es esencial para comprender. También es más fácil refactorizar este tipo de código.
Como se discutió anteriormente, dudo que esto haga una diferencia en la eficiencia. De hecho, yo diría que si el alcance es más local, ¡un compilador puede hacer más con él!
El primer código es mejor ya que restringe el alcance de la variable o
al bloque for
. Desde una perspectiva de rendimiento, podría no tener ningún efecto en Java, pero podría tener en compiladores de nivel inferior. Podrían poner la variable en un registro si haces la primera.
De hecho, algunas personas podrían pensar que si el compilador es tonto, el segundo fragmento es mejor en términos de rendimiento. ¡Esto es lo que me dijo un instructor en la universidad y me reí de él por esta sugerencia! Básicamente, los compiladores asignan memoria en la pila para las variables locales de un método solo una vez al inicio del método (ajustando el puntero de la pila) y lo liberan al final del método (otra vez ajustando el puntero de la pila, asumiendo que no es C ++ o no tiene ningún destructores para llamar). De modo que todas las variables locales basadas en pila en un método se asignan a la vez, sin importar dónde se declaran y cuánta memoria requieren. En realidad, si el compilador es tonto, no hay diferencia en términos de rendimiento, pero si es lo suficientemente inteligente, el primer código puede ser mejor ya que ayudará al compilador a comprender el alcance y el tiempo de vida de la variable. Por cierto, si es realmente inteligente, no debería haber absolutamente ninguna diferencia en el rendimiento, ya que infiere el alcance real.
La construcción de un objeto utilizando algo new
es totalmente diferente de simplemente declararlo, por supuesto.
Creo que la legibilidad es más importante que el rendimiento y, desde el punto de vista de la legibilidad, el primer código es definitivamente mejor.
La respuesta depende en parte de lo que hace el constructor y lo que sucede con el objeto después del bucle, ya que eso determina en gran medida cómo se optimiza el código.
Si el objeto es grande o complejo, absolutamente declararlo fuera del ciclo. De lo contrario, las personas que le dicen que no optimice prematuramente son las correctas.
Para llegar al corazón de esta pregunta ... [Tenga en cuenta que las implementaciones que no son de JVM pueden hacer las cosas de manera diferente si lo permite el JLS ...]
Primero, tenga en cuenta que la variable local "o" en el ejemplo es un puntero, no un objeto real.
Todas las variables locales se asignan en la pila de tiempo de ejecución en ranuras de 4 bytes. dobles y largos requieren dos ranuras; otros primitivos y punteros toman uno. (Incluso los booleanos toman una ranura completa)
Se debe crear un tamaño de pila de tiempo de ejecución para cada invocación de método. Este tamaño está determinado por las "ranuras" variables locales máximas que se necesitan en cualquier punto del método.
En el ejemplo anterior, ambas versiones del código requieren el mismo número máximo de variables locales para el método.
En ambos casos, se generará el mismo bytecode, actualizando la misma ranura en la pila de tiempo de ejecución.
En otras palabras, sin penalización de rendimiento en absoluto.
SIN EMBARGO, dependiendo del resto del código en el método, la versión de "declaración fuera del ciclo" en realidad podría requerir una mayor asignación de la pila de tiempo de ejecución. Por ejemplo, comparar
for (...) { Object o = ... }
for (...) { Object o = ... }
con
Object o;
for (...) { /* loop 1 */ }
for (...) { Object x =...; }
En el primer ejemplo, ambos bucles requieren la misma asignación de la pila en tiempo de ejecución.
En el segundo ejemplo, porque "o" vive más allá del bucle, "x" requiere una ranura de pila de tiempo de ejecución adicional.
Espero que esto ayude, - Scott
Para citar a Knuth, quien puede estar citando a Hoare :
La optimización prematura es la fuente de todos los males.
Si el compilador producirá código marginalmente más rápido definiendo la variable fuera del ciclo es discutible, y me imagino que no. Supongo que producirá un bytecode idéntico.
Compare esto con la cantidad de errores que probablemente evitará al determinar correctamente el alcance de su variable mediante la declaración in-loop ...
En realidad tengo delante de mí un código que se ve así:
for (int i = offset; i < offset + length; i++) {
char append = (char) (data[i] & 0xFF);
buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
char append = (char) (data[i] & 0xFF);
buffer.append(append);
}
...
for (int i = offset; i < offset + length; i++) {
char append = (char) (data[i] & 0xFF);
buffer.append(append);
}
Entonces, confiando en las capacidades del compilador, puedo suponer que solo habrá una asignación de pila para iy una para agregar . Entonces todo estaría bien, excepto el código duplicado.
Como nota al margen, se sabe que las aplicaciones Java son lentas. Nunca intenté hacer perfiles en Java, pero creo que el rendimiento alcanzado proviene principalmente de la administración de la asignación de memoria.
Cuando uso múltiples hilos (si está haciendo más de 50), entonces encontré que esta es una forma muy efectiva de manejar problemas de hilos fantasma:
Object one;
Object two;
Object three;
Object four;
Object five;
try{
for (int i=0; i<someValue; i++)
{
o = someList.get(i);
o.doSomething;
}
}catch(e){
e.printstacktrace
}
finally{
one = null;
two = null;
three = null;
four = null;
five = null;
System.gc();
}