Базовая оптимизация сортировки вставками делает код медленнее

Завтра я преподаю демо по сортировке вставками. Одной из важных оптимизаций является добавление проверки во внутренний цикл, которая останавливает его повторение после того, как вы поместите элемент в нужное положение. Итак, в основном, это происходит из этого:

public static void insertionSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        for (int j = i; j > 0; j--) {
            if (array[j] < array[j-1]) {
                int tmp = array[j];
                array[j] = array[j-1];
                array[j-1] = tmp;
            }
        }
    }
}

к этому:

public static void insertionSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        for (int j = i; j > 0 && array[j] < array[j-1]; j--) {
            int tmp = array[j];
            array[j] = array[j-1];
            array[j-1] = tmp;
        }
    }
}

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

Вот код, который я использую для сравнения:

import java.util.Arrays;
import java.util.Random;

public class InsertionSort {
    public static void main(String[] args) {
        int[] stuff = getRandomArray(50000);
        //System.out.println(Arrays.toString(stuff));

        long started = System.currentTimeMillis();
        insertionSort(stuff);
        long finished = System.currentTimeMillis();
        long totalTime = finished - started;

        //System.out.println(Arrays.toString(stuff));

        System.out.println("Started: " + started);
        System.out.println("Finished: " + finished);
        System.out.println("Elapsed: " + totalTime);
    }

    public static int[] getRandomArray(int size) {
        int[] array = new int[size];
        Random r = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = r.nextInt(size);
        }
        return array;
    }

    public static void insertionSort(int[] array) {
        // Implementation goes here
    }
}

Изменить: изменено количество элементов в тестовом массиве и закомментированы строки для печати.


person Michael Marvick    schedule 13.03.2015    source источник
comment
Какой предел slower у вас есть? Во второй версии есть дополнительное сравнение, и ваши массивы не такие длинные   -  person dhke    schedule 13.03.2015
comment
Ах, позвольте мне отредактировать это. Я замечаю это, когда приближаюсь к 50000 предметов   -  person Michael Marvick    schedule 13.03.2015
comment
Я получаю ~ 1200 мс для второй версии и ~ 1000 для первой, довольно стабильно. Не очень большой, но я ожидаю, что первый будет близок к N ^ 2 вычислениям, а второй будет ближе к N ^ 2 / 2. Второй алгоритм должен фактически иметь меньше сравнений, поскольку сравнение, которое находится в операторе if оператора первый алгоритм перемещается в условие цикла for во втором. Это должно привести к остановке цикла раньше.   -  person Michael Marvick    schedule 13.03.2015
comment
Это очень странно. Используя предоставленный вами код, я получаю около 2400 мс для первой версии и ~ 780 мс для второй версии на OpenJDK 8...   -  person dhke    schedule 13.03.2015
comment
Параметры JVM могут повлиять на результат теста, вы можете поделиться им, если у вас есть какие-либо конкретные параметры.   -  person İlker Korkut    schedule 13.03.2015
comment
Ответ, предоставленный @Uli ниже, сработал для меня! Это научило меня немного большему об оптимизации компилятора, чем я обычно думаю.   -  person Michael Marvick    schedule 13.03.2015


Ответы (1)


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

Найдите «микробенчмарк java», чтобы найти некоторые инструменты. Пример: http://java-performance.info/jmh/.

На всякий случай, если ваши результаты имеют смысл, я полагаю, что в вашем втором примере оптимизация цикла компилятора HotSpot не так эффективна, как в вашем первом примере.

person Uli    schedule 13.03.2015
comment
Благодарю вас! Это оказалось правильным ответом для меня. - person Michael Marvick; 13.03.2015
comment
Поскольку это используется в качестве примера на очень вводном занятии, я немного не решаюсь использовать специальные инструменты. Однако я обнаружил, что если я добавлю цикл для повторного запуска одного и того же теста несколько раз, последние результаты будут намного быстрее, чем предыдущие. Так что я собираюсь сделать это в демо, и это даст мне хороший шанс рассказать о том, что происходит, и о том, что последние прогоны более значимы. - person Michael Marvick; 13.03.2015