2012-02-17 11 views
7

Me han dicho en la escuela que es una mala práctica de modificar la variable de índice de un for loop:opción de JVM para optimizar sentencias de bucle

Ejemplo:

for(int i = 0 ; i < limit ; i++){ 
    if(something){ 
     i+=2;  //bad 
    } 
    if(something){ 
     limit+=2;  //bad 
    } 
} 

El argumento era que algún compilador la optimización puede optimizar el bucle y no recalcular el índice y el límite en cada bucle.

He hecho algunas pruebas en java y parece que por defecto el índice y el límite se recalculan cada vez.

Me pregunto si es posible activar este tipo de función en el JVM HotSpot?

Por ejemplo, para optimizar este tipo de bucle:

for(int i = 0 ; i < foo.getLength() ; i++){ } 

sin tener que escribir:

int length = foo.getLength() 
for(int i = 0 ; i < length ; i++){ } 

Es sólo un ejemplo Tengo curiosidad por probar y ver los alguna mejora.

EDITAR

Según Peter Lawrey responder por ello que en este sencillo ejemplo, la JVM no lo hacen en línea getLenght() método?:

public static void main(String[] args) { 
    Too t = new Too(); 
    for(int j=0; j<t.getLength();j++){ 
    } 
} 


class Too { 

    int l = 10; 
    public Too() { 
    } 
    public int getLength(){ 
     //System.out.println("test"); 
     return l; 
    } 
} 

En la "prueba" de salida de impresión es de 10 veces.

Creo que podría ser bueno optimizar este tipo de ejecución.

EDIT 2: Parece que hice un mal entendido ...

tengo retirar el println y de hecho el perfilador dime que el método getLength() ni siquiera se llama una vez en este caso.

+2

Parece que no entienden * lo que * en línea hace. El 101 de cada optimización del compilador es que el código producido puede ser funcionalmente equivalente al comportamiento que demanda el JLS. Esto significa que podemos alinear una llamada de función, pero NO PODEMOS eliminar una llamada 'println()'. Además, realmente no debería preocuparse por tales optimizaciones del compilador, o si lo hace, debe comprender al menos lo suficiente como para saber cómo probar este tipo de código. – Voo

+0

Ok, yo no sabía eso, soy bastante nuevo y todavía estoy aprendiendo mucho. Este tipo de "avance"? los conocimientos permanecen desapercibidos en (mi) escuela, por lo que trato de entender por mí mismo y a menudo cometo errores: s –

+0

Era tan libre y de hecho miré el código en detalle, para responder a su pregunta en breve: El JIT en línea 'getLength() 'bien, independientemente de si tiene la instrucción println() allí o no. Si desea los detalles que publicó un breve resumen a continuación;) – Voo

Respuesta

11

He hecho algunas pruebas en Java y parece que, por defecto, el índice y el límite se vuelven a calcular cada vez.

De acuerdo con la especificación del lenguaje Java, este:

for(int i = 0 ; i < foo.getLength() ; i++){ } 

significa que getLength() se llama en cada iteración del bucle. Los compiladores de Java son solo permitidos para mover la llamada getLength() fuera del ciclo si pueden efectivamente probar que no altera el comportamiento observable. (Por ejemplo, si getLength() simplemente devolvió el mismo valor de la misma variable cada vez, entonces existe una buena posibilidad de que el compilador JIT pueda alinear la llamada y luego deduzca que puede hacer la optimización de elevación. Pero si getLength() implica obtener la longitud de una recopilación concurrente o sincronizada, las posibilidades son escasas de que la optimización esté permitida ... debido a las acciones potenciales de otros hilos).

Eso es lo que un compilador permitió hacer.

Me pregunto si es posible activar este tipo de característica en el JVM HotSpot?

La simple respuesta es No.

Usted parece estar sugiriendo un modificador de compilador que dice/permite que el compilador de hacer caso omiso de las reglas de JLS. No hay tal cambio. Tal interruptor sería un MALA IDEA. Sería responsable de romper programas correctos/válidos/de trabajo. Considere esto:

class Test { 
    int count; 

    int test(String[] arg) { 
     for (int i = 0; i < getLength(arg); i++) { 
      // ... 
     } 
     return count; 
    } 

    int getLength(String[] arg) { 
     count++; 
     return arg.length; 
    } 
} 

Si el compilador se le permitió mover el getLength(arg) llamada fuera del circuito, sería cambiar el número de veces que el método se llama, y ​​por lo tanto cambiar el valor devuelto por el método test.

Las optimizaciones de Java que modifican el comportamiento de un programa Java correctamente escrito no son optimizaciones válidas. (Tenga en cuenta que el multi-threading tiende a enturbiar las aguas. El JLS, y específicamente las reglas del modelo de memoria, permiten que un compilador realice optimizaciones que podrían dar lugar a que diferentes hilos vean versiones inconsistentes del estado de la aplicación ... si no se sincronizan adecuadamente, lo que resulta en un comportamiento que no es correcta desde la perspectiva del desarrollador. Pero el verdadero problema es con la aplicación, no el compilador.)


Por cierto, un más convincente razón por la que no debería cambiar la variable de bucle en el cuerpo del bucle es que hace que su código sea más difícil de entender.

+0

Gracias por informaciones. Para la segunda parte de su respuesta, acepto que probablemente sea una mala idea habilitar (si es posible) esta característica con el programa de trabajo. Pero si comienzas un proyecto sabiendo estas reglas, creo que podría mejorar el rendimiento. –

+0

La única manera de saber si una parte de Java se ejecutará más rápido que otra es usar una herramienta de evaluación comparativa específicamente diseñada para Java, que calienta la JVM y todo. –

+0

@LouisWasserman Utilicé el perfilador netbeans. Estabas en 'println', mira mi edición. –

13

Depende de lo que haga foo.getLength(). Si puede ser inline, puede ser efectivamente lo mismo. Si no se puede alinear, la JVM no puede determinar si el resultado es el mismo.

Por cierto, puede escribir para un trazador de líneas.

for(int i = 0, length = foo.getLength(); i < length; i++){ } 

EDITAR: No vale la pena nada;

  • métodos y bucles generalmente no se optimizan hasta que se han llamado 10.000 veces.
  • perfiladores invocaciones de submuestras para reducir los gastos generales. Pueden contar cada 10 o 100 o más, por lo que un ejemplo trivial puede no aparecer.
+0

¡Gracias por los consejos! Por favor, eche un vistazo a mi edición. –

4

La razón principal para no hacer eso es que hace que sea mucho más difícil de entender y mantener el código.

Cualquiera que sea la optimización de JVM, no comprometerá la corrección del programa. Si no puede hacer una optimización porque el índice se modifica dentro del ciclo, entonces no lo optimizará. No veo cómo podría mostrarse una prueba Java si hay o no tal optimización.

De todos modos, Hotspot optimizará un montón de cosas para usted. Y su segundo ejemplo es un tipo de optimización explícita que Hotspot hará felizmente por usted.

+0

HotSpot es una misteriosa caja negra para mí, pero ¿realmente haría esa optimización? ¿Qué hay de los múltiples hilos que modifican el foo de modo que getLength() estaba cambiando, suponiendo que foo no es String, donde getLength es inmutable? Es decir. asume una colección mutable y usa getSize() en su lugar. –

+0

Para el segundo ejemplo, no estoy seguro de que el Hotspot JVM realice alguna optimización. Digamos 'foo.getLength()' return 10, luego el método 'getLength()' se ejecutará 10 veces. –

+1

Nunca verá una diferencia real en el comportamiento del programa debido a las optimizaciones. Si eliminó la línea 'System.out.println (" test ")', es completamente posible que la JVM _realice la optimización. –

2

Antes de entrar en más razonamiento por qué el acceso de campo no está en línea. Tal vez deberíamos mostrar que sí, si sabes lo que estás buscando (que en realidad no es trivial en Java), el acceso al campo está bien alineado.

Primero necesitamos una comprensión básica de cómo funciona el JIT, y realmente no puedo hacer eso en una respuesta. Baste decir que el JIT sólo funciona después de un acto que se ha llamado con bastante frecuencia (> 10 k por lo general)

Así que utilizar el siguiente código para la materia de prueba real:

public class Test { 
    private int length; 

    public Test() { 
     length = 10000; 
    } 

    public static void main(String[] args) { 
     for (int i = 0; i < 14000; i++) { 
      foo(); 
     } 
    } 

    public static void foo() { 
     Test bar = new Test(); 
     int sum = 0; 
     for (int i = 0; i < bar.getLength(); i++) { 
      sum += i; 
     } 
     System.out.println(sum); 
    } 

    public int getLength() { 
     System.out.print("_"); 
     return length; 
    }  
} 

Ahora compilar el código y correr con java.exe -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Test.foo Test >Test.txt que se traduce en una salida larga profano, pero la parte interesante es:

0x023de0e7: mov %esi,0x24(%esp) 
    0x023de0eb: mov %edi,0x28(%esp) 
    0x023de0ef: mov $0x38fba220,%edx ; {oop(a 'java/lang/Class' = 'java/lang/System')} 
    0x023de0f4: mov 0x6c(%edx),%ecx ;*getstatic out 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
    0x023de0f7: cmp (%ecx),%eax  ;*invokevirtual print 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
             ; implicit exception: dispatches to 0x023de29b 
    0x023de0f9: mov $0x3900e9d0,%edx ;*invokespecial write 
             ; - java.io.PrintStream::[email protected] 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
             ; {oop("_")} 
    0x023de0fe: nop  
    0x023de0ff: call 0x0238d1c0   ; OopMap{[32]=Oop off=132} 
             ;*invokespecial write 
             ; - java.io.PrintStream::[email protected] 
             ; - Test::[email protected] (line 24) 
             ; - Test::[email protected] (line 17) 
             ; {optimized virtual_call} 
    0x023de104: mov 0x20(%esp),%eax 
    0x023de108: mov 0x8(%eax),%ecx  ;*getfield length 
             ; - Test::[email protected] (line 25) 
             ; - Test::[email protected] (line 17) 
    0x023de10b: mov 0x24(%esp),%esi 
    0x023de10f: cmp %ecx,%esi 
    0x023de111: jl  0x023de0d8   ;*if_icmpge 
             ; - Test::[email protected] (line 17) 

que es el bucle interior que en realidad estamos ejecutando. Tenga en cuenta que el siguiente 0x023de108: mov 0x8(%eax),%ecx carga el valor de longitud en un registro: el material que está sobre él es para la llamada System.out (lo habría eliminado porque lo hace más complicado, pero como más de una persona pensó que esto dificultaría la creación de I) lo dejé allí). Incluso si no está en forma en el ensamble x86, puede ver claramente: No hay instrucciones de llamada en ningún lado, excepto la llamada de escritura nativa.

+0

Desafortunadamente en Ubuntu obtengo un archivo lleno de "_". No veo ninguna línea de mov ... –

+0

@alain Asegúrese de tener instalado el complemento necesario, y elimine mejor las declaraciones de impresión para comprobar que no se lo pierda (o instale una transmisión nula en el sistema. out) – Voo

+0

¡La solución ha sido encontrada aquí si alguien está interesado! Cómo usar -XX: + UnlockDiagnosticVMOptions -XX: CompileCommand = opción de impresión con JVM HotSpot –

Cuestiones relacionadas