usar create codigo java performance 32bit-64bit long-integer

create - textarea java netbeans



¿Por qué es mucho más lento que int en x64 Java? (7)

Acabo de escribir un punto de referencia usando una caliper .

Los results son bastante consistentes con el código original: una aceleración ~ 12x para usar int a lo long . Ciertamente parece que el ciclo de desenrollamiento reportado por tmyklebu o algo muy similar está sucediendo.

timeIntDecrements 195,266,845.000 timeLongDecrements 2,321,447,978.000

Este es mi código; tenga en cuenta que utiliza una instantánea recién construida de caliper , ya que no pude encontrar la forma de codificar contra su versión beta existente.

package test; import com.google.caliper.Benchmark; import com.google.caliper.Param; public final class App { @Param({""+1}) int number; private static class IntTest { public static int v; public static void reset() { v = Integer.MAX_VALUE; } public static boolean decrementAndCheck() { return --v < 0; } } private static class LongTest { public static long v; public static void reset() { v = Integer.MAX_VALUE; } public static boolean decrementAndCheck() { return --v < 0; } } @Benchmark int timeLongDecrements(int reps) { int k=0; for (int i=0; i<reps; i++) { LongTest.reset(); while (!LongTest.decrementAndCheck()) { k++; } } return (int)LongTest.v | k; } @Benchmark int timeIntDecrements(int reps) { int k=0; for (int i=0; i<reps; i++) { IntTest.reset(); while (!IntTest.decrementAndCheck()) { k++; } } return IntTest.v | k; } }

Estoy ejecutando Windows 8.1 x64 con Java 7 update 45 x64 (sin Java de 32 bits instalado) en una tableta Surface Pro 2.

El código siguiente toma 1688ms cuando el tipo de i es largo y 109ms cuando i es un int. ¿Por qué es largo (un tipo de 64 bits) un orden de magnitud más lento que int en una plataforma de 64 bits con una JVM de 64 bits?

Mi única especulación es que la CPU tarda más en agregar un entero de 64 bits que uno de 32 bits, pero parece poco probable. Sospecho que Haswell no usa sumadores de ripple-carry.

Estoy ejecutando esto en Eclipse Kepler SR1, por cierto.

public class Main { private static long i = Integer.MAX_VALUE; public static void main(String[] args) { System.out.println("Starting the loop"); long startTime = System.currentTimeMillis(); while(!decrementAndCheck()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the loop in " + (endTime - startTime) + "ms"); } private static boolean decrementAndCheck() { return --i < 0; } }

Editar: Aquí están los resultados del código equivalente de C ++ compilado por VS 2013 (abajo), mismo sistema. largo: 72265ms int: 74656ms Esos resultados estaban en modo de depuración de 32 bits.

En el modo de lanzamiento de 64 bits: de largo: 875ms long long: 906ms int: 1047ms

Esto sugiere que el resultado que observé es la rareza de la optimización de JVM en lugar de las limitaciones de la CPU.

#include "stdafx.h" #include "iostream" #include "windows.h" #include "limits.h" long long i = INT_MAX; using namespace std; boolean decrementAndCheck() { return --i < 0; } int _tmain(int argc, _TCHAR* argv[]) { cout << "Starting the loop" << endl; unsigned long startTime = GetTickCount64(); while (!decrementAndCheck()){ } unsigned long endTime = GetTickCount64(); cout << "Finished the loop in " << (endTime - startTime) << "ms" << endl; }

Editar: Acabo de probar esto de nuevo en Java 8 RTM, sin cambios significativos.


La pila de JVM se define en términos de palabras , cuyo tamaño es un detalle de implementación pero debe tener al menos 32 bits de ancho. El implementador de JVM puede usar palabras de 64 bits, pero el bytecode no puede confiar en esto, por lo que las operaciones con valores long o double deben manejarse con mucho cuidado. En particular, las instrucciones de bifurcación de enteros de JVM se definen exactamente en el tipo int .

En el caso de su código, el desmontaje es instructivo. Aquí está el bytecode para la versión int compilado por Oracle JDK 7:

private static boolean decrementAndCheck(); Code: 0: getstatic #14 // Field i:I 3: iconst_1 4: isub 5: dup 6: putstatic #14 // Field i:I 9: ifge 16 12: iconst_1 13: goto 17 16: iconst_0 17: ireturn

Tenga en cuenta que la JVM cargará el valor de su estática i (0), restará uno (3-4), duplicará el valor en la pila (5) y lo empujará de regreso a la variable (6). Luego hace una rama compare-with-zero y regresa.

La versión con el long es un poco más complicada:

private static boolean decrementAndCheck(); Code: 0: getstatic #14 // Field i:J 3: lconst_1 4: lsub 5: dup2 6: putstatic #14 // Field i:J 9: lconst_0 10: lcmp 11: ifge 18 14: iconst_1 15: goto 19 18: iconst_0 19: ireturn

Primero, cuando la JVM duplica el nuevo valor en la pila (5), tiene que duplicar dos palabras de la pila. En su caso, es muy posible que esto no sea más caro que duplicar uno, ya que la JVM puede usar libremente una palabra de 64 bits si es conveniente. Sin embargo, notará que la lógica de bifurcación es más larga aquí. La JVM no tiene una instrucción para comparar un long con cero, por lo que tiene que empujar un 0L constante en la pila (9), hacer una comparación long general (10) y luego ramificar en el valor de ese cálculo.

Aquí hay dos escenarios plausibles:

  • La JVM sigue exactamente la ruta del bytecode. En este caso, está haciendo más trabajo en la versión long , empujando y apareciendo varios valores extra, y estos están en la pila administrada virtual , no en la verdadera pila de CPU asistida por hardware. Si este es el caso, aún verá una diferencia de rendimiento significativa después del calentamiento.
  • La JVM se da cuenta de que puede optimizar este código. En este caso, tomar tiempo extra para optimizar parte de la lógica de empuje / comparación prácticamente innecesaria. Si este es el caso, verá muy poca diferencia de rendimiento después del calentamiento.

Te recomiendo que escribas una microdenominación correcta para eliminar el efecto de poner en marcha el JIT, y también intentar esto con una condición final que no sea cero, para forzar a la JVM a hacer la misma comparación en el int que hace con el long .


La unidad básica de datos en una máquina virtual Java es word. Elegir el tamaño de palabra correcto queda en la implementación de la JVM. Una implementación de JVM debe elegir un tamaño de palabra mínimo de 32 bits. Puede elegir un tamaño de palabra más alto para ganar eficiencia. Tampoco hay ninguna restricción de que una JVM de 64 bits deba elegir solo palabras de 64 bits.

La arquitectura subyacente no establece que el tamaño de la palabra también debe ser el mismo. JVM lee / escribe datos palabra por palabra. Esta es la razón por la cual podría tomar más tiempo por un largo que un int .

Here puedes encontrar más sobre el mismo tema.


Mi JVM hace esto bastante sencillo al ciclo interno cuando usas long s:

0x00007fdd859dbb80: test %eax,0x5f7847a(%rip) /* fun JVM hack */ 0x00007fdd859dbb86: dec %r11 /* i-- */ 0x00007fdd859dbb89: mov %r11,0x258(%r10) /* store i to memory */ 0x00007fdd859dbb90: test %r11,%r11 /* unnecessary test */ 0x00007fdd859dbb93: jge 0x00007fdd859dbb80 /* go back to the loop top */

Es una trampa, difícil, cuando usas int s; en primer lugar, hay algo pervertido que no pretendo entender, pero que parece una configuración para un ciclo desenrollado:

0x00007f3dc290b5a1: mov %r11d,%r9d 0x00007f3dc290b5a4: dec %r9d 0x00007f3dc290b5a7: mov %r9d,0x258(%r10) 0x00007f3dc290b5ae: test %r9d,%r9d 0x00007f3dc290b5b1: jl 0x00007f3dc290b662 0x00007f3dc290b5b7: add $0xfffffffffffffffe,%r11d 0x00007f3dc290b5bb: mov %r9d,%ecx 0x00007f3dc290b5be: dec %ecx 0x00007f3dc290b5c0: mov %ecx,0x258(%r10) 0x00007f3dc290b5c7: cmp %r11d,%ecx 0x00007f3dc290b5ca: jle 0x00007f3dc290b5d1 0x00007f3dc290b5cc: mov %ecx,%r9d 0x00007f3dc290b5cf: jmp 0x00007f3dc290b5bb 0x00007f3dc290b5d1: and $0xfffffffffffffffe,%r9d 0x00007f3dc290b5d5: mov %r9d,%r8d 0x00007f3dc290b5d8: neg %r8d 0x00007f3dc290b5db: sar $0x1f,%r8d 0x00007f3dc290b5df: shr $0x1f,%r8d 0x00007f3dc290b5e3: sub %r9d,%r8d 0x00007f3dc290b5e6: sar %r8d 0x00007f3dc290b5e9: neg %r8d 0x00007f3dc290b5ec: and $0xfffffffffffffffe,%r8d 0x00007f3dc290b5f0: shl %r8d 0x00007f3dc290b5f3: mov %r8d,%r11d 0x00007f3dc290b5f6: neg %r11d 0x00007f3dc290b5f9: sar $0x1f,%r11d 0x00007f3dc290b5fd: shr $0x1e,%r11d 0x00007f3dc290b601: sub %r8d,%r11d 0x00007f3dc290b604: sar $0x2,%r11d 0x00007f3dc290b608: neg %r11d 0x00007f3dc290b60b: and $0xfffffffffffffffe,%r11d 0x00007f3dc290b60f: shl $0x2,%r11d 0x00007f3dc290b613: mov %r11d,%r9d 0x00007f3dc290b616: neg %r9d 0x00007f3dc290b619: sar $0x1f,%r9d 0x00007f3dc290b61d: shr $0x1d,%r9d 0x00007f3dc290b621: sub %r11d,%r9d 0x00007f3dc290b624: sar $0x3,%r9d 0x00007f3dc290b628: neg %r9d 0x00007f3dc290b62b: and $0xfffffffffffffffe,%r9d 0x00007f3dc290b62f: shl $0x3,%r9d 0x00007f3dc290b633: mov %ecx,%r11d 0x00007f3dc290b636: sub %r9d,%r11d 0x00007f3dc290b639: cmp %r11d,%ecx 0x00007f3dc290b63c: jle 0x00007f3dc290b64f 0x00007f3dc290b63e: xchg %ax,%ax /* OK, fine; I know what a nop looks like */

luego, el bucle desenrollado en sí mismo:

0x00007f3dc290b640: add $0xfffffffffffffff0,%ecx 0x00007f3dc290b643: mov %ecx,0x258(%r10) 0x00007f3dc290b64a: cmp %r11d,%ecx 0x00007f3dc290b64d: jg 0x00007f3dc290b640

luego, el código de desmontaje para el bucle desenrollado, en sí mismo una prueba y un bucle directo:

0x00007f3dc290b64f: cmp $0xffffffffffffffff,%ecx 0x00007f3dc290b652: jle 0x00007f3dc290b662 0x00007f3dc290b654: dec %ecx 0x00007f3dc290b656: mov %ecx,0x258(%r10) 0x00007f3dc290b65d: cmp $0xffffffffffffffff,%ecx 0x00007f3dc290b660: jg 0x00007f3dc290b654

Así que va 16 veces más rápido para las entradas porque el JIT desenrolló el bucle int 16 veces, pero no desenrolló el bucle long en absoluto.

Para completar, aquí está el código que en realidad probé:

public class foo136 { private static int i = Integer.MAX_VALUE; public static void main(String[] args) { System.out.println("Starting the loop"); for (int foo = 0; foo < 100; foo++) doit(); } static void doit() { i = Integer.MAX_VALUE; long startTime = System.currentTimeMillis(); while(!decrementAndCheck()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the loop in " + (endTime - startTime) + "ms"); } private static boolean decrementAndCheck() { return --i < 0; } }

Los volcados de conjunto se generaron utilizando las opciones -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly . Tenga en cuenta que necesita perder el tiempo con la instalación de JVM para que esto también funcione para usted; necesita colocar alguna biblioteca compartida aleatoria exactamente en el lugar correcto o fallará.


No tengo una máquina de 64 bits para probar, pero la gran diferencia sugiere que hay más que un bytecode ligeramente más largo en el trabajo.

Veo tiempos muy cercanos para long / int (4400 vs 4800ms) en mi 32 bit 1.7.0_45.

Esto es solo una suposición , pero sospecho fuertemente que es el efecto de una penalización de desalineación de la memoria. Para confirmar / denegar la sospecha, intente agregar una dummy public static pública = 0; antes de la declaración de i. Eso empujará hacia abajo 4 bytes en el diseño de la memoria y puede alinearlo correctamente para un mejor rendimiento. Confirmado que no está causando el problema.

EDITAR: El razonamiento detrás de esto es que la máquina virtual no puede reordenar los campos a su antojo agregando relleno para una alineación óptima, ya que eso puede interferir con JNI (No es el caso).


Para el registro, esta versión hace un "calentamiento" crudo:

public class LongSpeed { private static long i = Integer.MAX_VALUE; private static int j = Integer.MAX_VALUE; public static void main(String[] args) { for (int x = 0; x < 10; x++) { runLong(); runWord(); } } private static void runLong() { System.out.println("Starting the long loop"); i = Integer.MAX_VALUE; long startTime = System.currentTimeMillis(); while(!decrementAndCheckI()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the long loop in " + (endTime - startTime) + "ms"); } private static void runWord() { System.out.println("Starting the word loop"); j = Integer.MAX_VALUE; long startTime = System.currentTimeMillis(); while(!decrementAndCheckJ()){ } long endTime = System.currentTimeMillis(); System.out.println("Finished the word loop in " + (endTime - startTime) + "ms"); } private static boolean decrementAndCheckI() { return --i < 0; } private static boolean decrementAndCheckJ() { return --j < 0; } }

Los tiempos totales mejoran alrededor del 30%, pero la relación entre los dos sigue siendo aproximadamente la misma.


Por los récords:

si uso

boolean decrementAndCheckLong() { lo = lo - 1l; return lo < -1l; }

(cambiado "l--" a "l = l - 1l") el rendimiento prolongado mejora en ~ 50%