Сравнительный анализ небольших массивов по сравнениюСписки на Java:Мой код бенчмаркинга неверен?

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

Вопрос

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

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

Что я хотел бы знать, так это:

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

JVM - это Java 1.6.0_17 на OSX, и она определенно работает в режиме горячей точки.

  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;
    }
}
Это было полезно?

Решение

Микробенчмарки очень, очень сложно получить прямо на такой платформе, как 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;
    }
}

Другие советы

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top