Почему время выполнения программы каждый раз разное и иногда Vector работает якобы быстрее, чем ArrayList?

Решил проверить, насколько быстрее несинхронизированный ArrayList, чем потокобезопасный Vector. Для замера потраченного времени использовал System.nanoTime(). Написал метод:

private static void battleOfArrayListAndVector() { 
    System.out.println("--------------------------------------------------");
    System.out.println("Битва: Vector vs. ArrayList:");
    long startTime = System.nanoTime();
    Vector<String> vector = new Vector<>();
    vector.add("Java");
    vector.add("C++");
    vector.add(1, "C");
    vector.add(null);
    vector.add("");
    vector.addAll(Arrays.asList("Python", "JavaScript", "HTML", "CSS"));
    vector.remove(vector.size() - 1);
    vector.removeAll(Arrays.asList(null, ""));
    vector.retainAll(Arrays.asList("Java", "C++"));
    System.out.println("Есть элемент \"PHP\"? " + vector.contains("PHP"));
    long endTime = System.nanoTime();
    long vectorTime = endTime - startTime;
    System.out.println("Vector потратил " + vectorTime + " наносекунд");
    startTime = System.nanoTime();
    List<String> arrayList = new ArrayList<>();
    arrayList.add("Java");
    arrayList.add("C++");
    arrayList.add(1, "C");
    arrayList.add(null);
    arrayList.add("");
    arrayList.addAll(Arrays.asList("Python", "JavaScript", "HTML", "CSS"));
    arrayList.remove(vector.size() - 1);
    arrayList.removeAll(Arrays.asList(null, ""));
    arrayList.retainAll(Arrays.asList("Java", "C++"));
    System.out.println("Есть элемент \"PHP\"? " + arrayList.contains("PHP"));
    endTime = System.nanoTime();
    long arrayListTime = endTime - startTime;
    System.out.println("ArrayList потратил " + arrayListTime + " наносекунд");
    System.out.println("ArrayList сэкономил: " + (vectorTime - arrayListTime) + " наносекунд.");
    System.out.println("--------------------------------------------------");
}

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

(1)
--------------------------------------------------
Битва: Vector vs. ArrayList:
Есть элемент "PHP"? false
Vector потратил 3994242 наносекунд
Есть элемент "PHP"? false
ArrayList потратил 3300205 наносекунд
ArrayList сэкономил: 694037 наносекунд.
--------------------------------------------------
(2)
--------------------------------------------------
Битва: Vector vs. ArrayList:
Есть элемент "PHP"? false
Vector потратил 4024390 наносекунд
Есть элемент "PHP"? false
ArrayList потратил 3704312 наносекунд
ArrayList сэкономил: 320078 наносекунд.
--------------------------------------------------
(3)
--------------------------------------------------
Битва: Vector vs. ArrayList:
Есть элемент "PHP"? false
Vector потратил 1880058 наносекунд
Есть элемент "PHP"? false
ArrayList потратил 3483657 наносекунд
ArrayList сэкономил: -1603599 наносекунд.
--------------------------------------------------

В последнем случае ArrayList вообще оказался медленней. Как так? Почему время исполнения программы постоянно меняется и иногда вместе с выводом исследования?


Ответы (1 шт):

Автор решения: Зонтик

Вкратце, ArrayList работает быстрее вектора, просто я накосячил. Немного переписал код проверки, прогнав тест 10000 раз, чтобы узнать, сколько раз ArrayList выигрывает. Получилось, что где-то около 7750 из 10000.

Как я понял, JVM "занимается" своими внутренними процессами (например, сборка мусора) и это фальсифицирует результаты.

Решение проблемы:

  1. Добавил вызов System.gc() для предотвращения активности garbage collector'а — это рекомендация отсюда.
  2. По совету @NowhereMan вынес создание листов для запусков метода addAll() в отдельные статические переменные.
  3. Закрыл все сторонние программы, оставил только командную строку, которая нужна для запуска.
  4. Запустил программу с флагами -Xbatch и -Xcomp, чтобы JVM не вздумала оптимизировать работу программы каждый раз:
java -Xbatch -Xcomp theory/collections/UseVector

В результате ArrayList стал выигрывать в 9996 и более случаях из десяти тысяч. Иногда даже до максимума доходило.

Финальный код:

public static void battleOfVectorAndArrayList() {
    int counterOfArrayListWins = 0;
    for(int i = 0; i < 10000; i++)  {
        System.gc(); 
        long time = getWinOfArrayList();
        if(time > 0) counterOfArrayListWins++;
    }
    System.out.println("ArrayList выиграл " + counterOfArrayListWins + " раз.");
}
    
private static List<String> list1 = Arrays.asList("Python", "JavaScript", "HTML", "CSS");
private static List<String> list2 = Arrays.asList("", null);
private static List<String> list3 = Arrays.asList("Java", "C++");
    
private static long getWinOfArrayList() {
    long startTime = System.nanoTime();
    Vector<String> vector = new Vector<>();
    vector.add("Java");
    vector.add("C++");
    vector.add(1, "C");
    vector.add(null);
    vector.add("");
    vector.addAll(list1);
    vector.remove(vector.size() - 1);
    vector.removeAll(list2);
    vector.retainAll(list3);
    long endTime = System.nanoTime();
    long vectorTime = endTime - startTime;
    startTime = System.nanoTime();
    List<String> arrayList = new ArrayList<>();
    arrayList.add("Java");
    arrayList.add("C++");
    arrayList.add(1, "C");
    arrayList.add(null);
    arrayList.add("");
    arrayList.addAll(list1);
    arrayList.remove(arrayList.size() - 1);
    arrayList.removeAll(list2);
    arrayList.retainAll(list3);
    endTime = System.nanoTime();
    long arrayListTime = endTime - startTime;
    return vectorTime - arrayListTime;
}
→ Ссылка