Сравнительный анализ небольших массивов и списков в Java: мой контрольный код неверен? - PullRequest
3 голосов
/ 09 февраля 2010

Отказ от ответственности: Я просмотрел это вопрос и этот вопрос но они оба потерпели крушение маленькими детали и общее Оптимизация - ненужные проблемы. Мне действительно нужно все представление, которое я можно получить в моем текущем приложении, которое получение-обработка-извергание MIDI-данных в настоящее время. Также необходимо увеличить как можно лучше.

Я сравниваю производительность array при большом количестве операций чтения для небольших списков с ArrayList, а также с наличием переменных в руках. Я обнаружил, что массив бьет ArrayList в 2,5 раза, а даже бьет, просто имея ссылки на объект.

Я бы хотел знать:

  1. С моим тестом все в порядке? Я изменил порядок испытаний и количество запусков без изменений . Я также использовал миллисекунды вместо наносекунд, но безрезультатно.
  2. Должен ли я указывать какие-либо параметры Java, чтобы минимизировать эту разницу?
  3. Если эта разница реальна, в этом случае не следует ли мне предпочесть Test[] ArrayList<Test> в этой ситуации и ввести код, необходимый для их преобразования? Очевидно, я читаю много больше, чем писать.

JVM - это Java 1.6.0_17 на OSX, и она определенно работает в режиме 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;
    }
}

Ответы [ 3 ]

1 голос
/ 09 февраля 2010

Микробенчмарки очень и очень трудно получить прямо на платформе, такой как Java. Вы определенно должны извлечь код, который нужно сравнить, в отдельные методы, прогнать их несколько раз для прогрева и затем измерить. Я сделал это (код ниже), и в результате прямой доступ по ссылкам в три раза быстрее, чем через массив, но коллекция все еще медленнее в 2 раза.

Эти цифры основаны на опциях JVM -server -XX:+DoEscapeAnalysis. Без -server использование коллекции будет радикально медленнее (но, как ни странно, прямой доступ и доступ к массиву немного быстрее, что указывает на то, что происходит что-то странное). -XX:+DoEscapeAnalysis дает еще 30% ускорение для коллекции, но очень сомнительно, будет ли она работать так же для вашего реального производственного кода.

В целом мой вывод будет таким: забудь о микробенчмарках, они могут слишком легко вводить в заблуждение. Измеряйте как можно ближе к рабочему коду, не переписывая все приложение.

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;
    }
}
1 голос
/ 09 февраля 2010

Ваш эталонный тест действителен только в том случае, если ваш фактический вариант использования соответствует коду эталонного теста, то есть очень мало операций над каждым элементом, так что время выполнения в значительной степени определяется временем доступа, а не самими операциями. Если это так, то да, вы должны использовать массивы, если производительность критична. Однако, если ваш реальный вариант использования требует намного больше фактических вычислений для каждого элемента, то время доступа для каждого элемента станет намного менее значительным.

0 голосов
/ 09 февраля 2010

Вероятно, это не верно. Если я понимаю, как работают JIT-компиляторы, компиляция метода не повлияет на вызов этого метода, который уже выполняется. Поскольку метод main вызывается только один раз, он в конечном итоге будет интерпретирован, и, поскольку большая часть работы выполняется в теле этого метода, полученные вами числа не будут особенно показывать нормальное выполнение.

Эффекты компиляции JIT могут каким-то образом объяснить, почему случай без сборок был медленнее, чем случай с массивами. Этот результат нелогичен и ставит под сомнение другой результат теста, о котором вы сообщили.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...