Evaluación comparativa de las pequeñas matrices vs. Listas en Java: ¿Es malo mi código evaluación comparativa?

StackOverflow https://stackoverflow.com/questions/2227947

Pregunta

  

Renuncia: He mirado a través este   pregunta y esta pregunta   pero ambos se descarrilaron por pequeña   detalles y general   optimización-es-innecesarias preocupaciones.   Realmente necesito todo el rendimiento de E   puede ponerse en mi aplicación actual, que es   recibir de procesamiento que escupe datos MIDI   en tiempo real. También se necesita para ampliar    lo mejor posible.

Estoy comparando el rendimiento array en un alto número de lecturas para las pequeñas listas para ArrayList y también para sólo tener las variables en la mano. Estoy encontrando que una serie late ArrayList por un factor de 2,5 y incluso mejor que sólo tener las referencias a objetos.

Lo que me gustaría saber es:

  1. ¿Está bien mi criterio? He cambiado el orden de las pruebas y número de carreras sin cambios . También he utilizado milisegundos en lugar de nanosegundos en vano.
  2. ¿Debo especificar ninguna opción Java para minimizar esta diferencia?
  3. Si esta diferencia es real, en este caso No debería yo prefiero Test[] a ArrayList<Test> en esta situación y poner en el código necesario para convertirlos? Obviamente estoy leyendo mucho más que la escritura .

JVM es Java 1.6.0_17 en OSX y es definitivamente ejecuta en modo hotspot.

  public class ArraysVsLists {

    static int RUNS = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        t1 = System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        t1= System.nanoTime();

        for (int i=0; i<RUNS; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1) + " How long array ");

    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}
¿Fue útil?

Solución

microbenchmarks son muy, muy duro para llegar a la derecha en una plataforma como Java. Sin duda hay que extraer el código a partir de referencias a métodos separadas, ejecutar unos pocos miles de veces más calentamiento y luego medir. He hecho eso (código de abajo) y el resultado es que el acceso directo a través de referencias es luego tres veces más rápido que a través de una matriz, pero la colección sigue siendo más lento por un factor de 2.

Estas cifras se basan en la JVM opciones -server -XX:+DoEscapeAnalysis. Sin -server, utilizando la colección es drásticamente más lenta (pero extrañamente, acceso directo y la matriz son un poco más rápido, lo que indica que hay algo raro pasando). -XX:+DoEscapeAnalysis produce otro aumento de velocidad del 30% para la colección, pero es mucho questionabled si va a funcionar tan bien para su código de producción real.

En general, mi conclusión sería: olvidarse de microbenchmarks, pueden ser fácilmente engañosa. Medir lo más cerca posible de código de producción que pueda, sin tener que volver a escribir toda la aplicación.

import java.util.ArrayList;

public class ArrayTest {

    static int RUNS_INNER = 1000;
    static int RUNS_WARMUP = 10000;
    static int RUNS_OUTER = 100000;

    public static void main(String[] args) {
        long t1;
        long t2;

        Test test1 = new Test();
        test1.thing = (int)Math.round(100*Math.random());
        Test test2 = new Test();
        test2.thing = (int)Math.round(100*Math.random());

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testRefs(test1, test2);            
        }
        t1 = System.nanoTime();
        for(int i=0; i<RUNS_OUTER; i++)
        {
            testRefs(test1, test2);            
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long NO collection");

        ArrayList<Test> list = new ArrayList<Test>(1);
        list.add(test1);
        list.add(test2);
        // tried this too: helps a tiny tiny bit 
        list.trimToSize();

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testColl(list);
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testColl(list);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long collection");


        Test[] array = new Test[2];
        list.toArray(array);

        for(int i=0; i<RUNS_WARMUP; i++)
        {
            testArr(array);            
        }
        t1= System.nanoTime();

        for(int i=0; i<RUNS_OUTER; i++)
        {
            testArr(array);
        }

        t2 = System.nanoTime();
        System.out.println((t2-t1)/1000000.0 + " How long array ");

    }

    private static void testArr(Test[] array)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test test : array) {
                test.changeThing(i);
            }
        }
    }

    private static void testColl(ArrayList<Test> list)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            for (Test eachTest : list) {
                eachTest.changeThing(i);
            }
        }
    }

    private static void testRefs(Test test1, Test test2)
    {
        for (int i=0; i<RUNS_INNER; i++) {
            test1.changeThing(i);
            test2.changeThing(i);
        }
    }
}

class Test {
    int thing;
    int thing2;
    public void changeThing(int addThis) {
        thing2 = addThis + thing;
    }
}

Otros consejos

Su punto de referencia es válida únicamente si su caso de uso real coincida con el código de referencia, es decir, muy pocas operaciones sobre cada elemento, de manera que el tiempo de ejecución está determinada en gran medida por el tiempo de acceso en lugar de las propias operaciones. Si ese es el caso, entonces sí, usted debe utilizar matrices si el rendimiento es crítico. Sin embargo, si su caso de uso real, implica una gran cantidad de cálculo más real por elemento, entonces el tiempo de acceso por elemento será mucho menos significativo.

Es probable que no es válido. Si entiendo la forma en que funcionan los compiladores JIT, la compilación de un método no afectará una llamada a ese método que ya se está ejecutando. Dado que el método main sólo se le llama una vez, va a terminar siendo interpretado, y dado que la mayor parte del trabajo se hace en el cuerpo de ese método, los números que se obtienen no será particularmente indicativo de ejecución normal.

JIT efectos de compilación pueden de alguna manera a explicar por qué el caso no se recojan fue más lento que el caso matrices. Este resultado es contrario a la intuición, y se coloca una duda en el otro resultado del benchmark que se ha informado.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top