lenguaje - Evaluación comparativa de Java: ¿por qué es el segundo ciclo más rápido?
lenguaje java (6)
Tengo curiosidad sobre esto.
Quería comprobar qué función era más rápida, así que creo un pequeño código y lo ejecuté muchas veces.
public static void main(String[] args) {
long ts;
String c = "sgfrt34tdfg34";
ts = System.currentTimeMillis();
for (int k = 0; k < 10000000; k++) {
c.getBytes();
}
System.out.println("t1->" + (System.currentTimeMillis() - ts));
ts = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Bytes.toBytes(c);
}
System.out.println("t2->" + (System.currentTimeMillis() - ts));
}
El "segundo" ciclo es más rápido, entonces, pensé que la clase Bytes de hadoop era más rápida que la función de la clase String. Luego, cambié el orden de los bucles y luego c.getBytes () se hizo más rápido. Ejecuté muchas veces, y mi conclusión fue que no sé por qué, pero sucede algo en mi máquina virtual después de la ejecución del primer código, de modo que los resultados se vuelven más rápidos para el segundo ciclo.
El "segundo" ciclo es más rápido, entonces,
Cuando ejecuta un método al menos 10000 veces, desencadena el método completo para compilarse. Esto significa que su segundo bucle puede ser
- más rápido ya que está compilado la primera vez que lo ejecuta.
- más lento porque cuando está optimizado no tiene buena información / contadores sobre cómo se ejecuta el código.
La mejor solución es colocar cada bucle en un método separado para que un bucle no optimice el otro Y ejecutar esto unas pocas veces, ignorando la primera ejecución.
p.ej
for(int i = 0; i < 3; i++) {
long time1 = doTest1(); // timed using System.nanoTime();
long time2 = doTest2();
System.out.printf("Test1 took %,d on average, Test2 took %,d on average%n",
time1/RUNS, time2/RUNS);
}
Este es un clásico problema de evaluación comparativa de Java. Hotspot / JIT / etc compilará su código a medida que lo utiliza, por lo que se vuelve más rápido durante la ejecución.
Ejecute el ciclo al menos 3000 veces (10000 en un servidor o en 64 bits) primero, luego haga sus mediciones.
Lo más probable es que el código aún estuviera compilando o aún no compilado en el momento en que se ejecutó el primer bucle.
Envuelva todo el método en un bucle externo para que pueda ejecutar los puntos de referencia varias veces, y debería ver resultados más estables.
Pocas observaciones más
Como señala @dasblinkenlight arriba, Hadoop''s
Bytes.toBytes(c);
internamente llama aString.getBytes("UTF-8")
El método de variante
String.getBytes()
que toma el conjunto de caracteres como entrada es más rápido que el que no toma ningún juego de caracteres. Entonces, para una cadena dada,getBytes("UTF-8")
sería más rápido quegetBytes()
. He probado esto en mi máquina (Windows8, JDK 7). Ejecute los dos bucles uno congetBytes("UTF-8")
y otro congetBytes()
en secuencia en iteraciones iguales.long ts; String c = "sgfrt34tdfg34"; ts = System.currentTimeMillis(); for (int k = 0; k < 10000000; k++) { c.getBytes("UTF-8"); } System.out.println("t1->" + (System.currentTimeMillis() - ts)); ts = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { c.getBytes(); } System.out.println("t2->" + (System.currentTimeMillis() - ts));
esto da:
t1->1970
t2->2541
y los resultados son los mismos incluso si cambia el orden de las ejecuciones de loop. Para descartar cualquier optimización JIT, sugeriría ejecutar las pruebas en métodos separados para confirmar esto (como lo sugirió @Peter Lawrey más arriba)
- Por lo tanto,
Bytes.toBytes(c)
siempre debe ser más rápido queString.getBytes()
Sabe que hay algo mal, porque Bytes.toBytes
llama internamente a c.getBytes
:
public static byte[] toBytes(String s) {
try {
return s.getBytes(HConstants.UTF8_ENCODING);
} catch (UnsupportedEncodingException e) {
LOG.error("UTF-8 not supported?", e);
return null;
}
}
La fuente está tomada desde aquí . Esto le dice que la llamada no puede ser más rápida que la llamada directa: en el mejor de los casos (es decir, si se inline), tendrá el mismo tiempo. En general, sin embargo, esperas que sea un poco más lento, debido a la pequeña sobrecarga de llamar a una función.
Este es el problema clásico con la micro-evaluación comparativa en entornos interpretados y recolectados de basura con componentes que se ejecutan en un tiempo arbitrario, como recolectores de basura. Además de eso, hay optimizaciones de hardware, como el almacenamiento en caché, que sesgan la imagen. Como resultado, la mejor manera de ver lo que sucede a menudo es mirar la fuente.
Simplemente podría ser el caso de que asigne tanto espacio para objetos con sus llamadas a getBytes (), que JVM Garbage Collector se inicie y limpie las referencias no utilizadas (sacando la basura).