тот же код, тот же ввод, иногда работает быстро, иногда медленно, почему? - PullRequest
5 голосов
/ 03 декабря 2011

Я написал несколько классов Java для оценки / демонстрации различных алгоритмов сортировки. Однако, я запутался, когда я запускаю свой демонстрационный класс. Надеюсь, вы, ребята, можете дать мне объяснение. (этот вопрос НЕ домашнее задание.)

Сначала я бы перечислил несколько кодов, связанных с этим вопросом.

AbstractDemo

public abstract class AbstractDemo {
    protected final int BIG_ARRAY_SIZE = 20000;
    protected final int SMALL_ARRAY_SIZE = 14;
    protected Stopwatch stopwatch = new Stopwatch();

    public final void doDemo() {
        prepareDemo();
        specificDemo();
    }

    protected abstract void prepareDemo();

    protected abstract void specificDemo();

    protected final void printInfo(final String text) {
        System.out.println(text);
    }
}

SortingDemo

public class SortingDemo extends AbstractDemo {
    private static final String FMT = "%-10s| %-21s| %7s ms.";
    private static final String SPL = AlgUtil.lineSeparator('-', 45);
    private static final String SPLT = AlgUtil.lineSeparator('=', 45);

    private int[] data;

    private final List<Sorting> demoList = new LinkedList<Sorting>();

    @Override
    protected void specificDemo() {
        int[] testData;
        //*** this comment is interesting!!! for (int x = 1; x < 6; x++) {  

            printInfo(String.format("Sorting %7s elements", data.length));
            printInfo(SPLT);
            for (final Sorting sort : demoList) {

                // here I made a copy of the original Array, avoid to sort an already sorted array.
                testData = new int[data.length];
                System.arraycopy(data, 0, testData, 0, data.length);
                stopwatch.start();
                // sort
                sort.sort(testData);
                stopwatch.stop();
                printInfo(String.format(FMT, sort.getBigO(), sort.getClass().getSimpleName(), stopwatch.read()));
                printInfo(SPL);
                testData = null;
                stopwatch.reset();
            }
        //}
    }

    @Override
    protected void prepareDemo() {
        data = AlgUtil.getRandomIntArray(BIG_ARRAY_SIZE, BIG_ARRAY_SIZE * 5, false);
        demoList.add(new InsertionSort());
        demoList.add(new SelectionSort());
        demoList.add(new BubbleSort());
        demoList.add(new MergeSort());  //here is interesting too
        demoList.add(new OptimizedMergeSort());

    }

    public static void main(final String[] args) {
        final AbstractDemo sortingDemo = new SortingDemo();
        sortingDemo.doDemo();
    }
}

Секундомер

public class Stopwatch {
    private boolean running;
    private long startTime;
    private long elapsedMillisec;

    public void start() {
        if (!running) {
            this.startTime = System.currentTimeMillis();
            running = true;
        } else {
            throw new IllegalStateException("the stopwatch is already running");
        }
    }

    public void stop() {
        if (running) {
            elapsedMillisec = System.currentTimeMillis() - startTime;
            running = false;
        } else {
            throw new IllegalStateException("the stopwatch is not running");
        }
    }

    public void reset() {
        elapsedMillisec = 0;

    }

    public long read() {
        if (running) {
            elapsedMillisec = System.currentTimeMillis() - startTime;
        }
        return this.elapsedMillisec;
    }

}

метод генерации случайного массива

public static int[] getRandomIntArray(final int len, final int max, boolean allowNegative) {
        final int[] intArray = new int[len];
        final Random rand = new Random();
        rand.setSeed(20100102);

        if (!allowNegative) {
            if (max <= 0) {
                throw new IllegalArgumentException("max must be possitive if allowNegative false");
            }
            for (int i = 0; i < intArray.length; i++) {
                intArray[i] = rand.nextInt(max);
            }
        } else {
            int n;
            int i = 0;
            while (i < len) {
                n = rand.nextInt();
                if (n < max) {
                    intArray[i] = n;
                    i++;
                }
            }
        }

        return intArray;
    }

вы можете видеть, я генерирую массив int с 20000 элементами. И так как у меня есть фиксированное начальное число в методе getRandomIntArray, у меня всегда один и тот же массив каждый раз, когда я его вызываю. Класс SortingDemo имеет метод main, если я запускаю этот класс, я получаю вывод:

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |     101 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     667 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |    1320 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |      39 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |      11 ms.
---------------------------------------------

выглядит хорошо. Теперь приходит то, что меня смутило. Если я изменю последовательность demoList.add () в SortingDemo, скажем:

demoList.add(new InsertionSort());
    demoList.add(new SelectionSort());
    demoList.add(new BubbleSort());
    // OptimizedMergeSort before Mergesort
    demoList.add(new OptimizedMergeSort()); 
    demoList.add(new MergeSort());

Я получил:

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |     103 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     676 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |    1313 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |      41 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |      14 ms.
---------------------------------------------

почему вывод отличается от первого запуска? OptimizedMergeSort занял больше времени, чем обычно MergeSort ...

И если я раскомментирую строку for (int x=1; x<6; x++) в SortingDemo, (запустив тест с тем же массивом 5 раз), я получу:

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |     101 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     668 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |    1311 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |      37 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |      10 ms.
---------------------------------------------

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |      94 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     665 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |    1308 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |       5 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |       7 ms.
---------------------------------------------

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |     116 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     318 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |     969 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |       5 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |      10 ms.
---------------------------------------------

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |     116 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     319 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |     964 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |       5 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |       5 ms.
---------------------------------------------

Sorting   20000 elements
=============================================
O(n^2)    | InsertionSort        |     116 ms.
---------------------------------------------
O(n^2)    | SelectionSort        |     320 ms.
---------------------------------------------
O(n^2)    | BubbleSort           |     963 ms.
---------------------------------------------
O(?)      | OptimizedMergeSort   |       4 ms.
---------------------------------------------
O(nlog(n))| MergeSort            |       6 ms.
---------------------------------------------

Для других сортировок результат выглядит приемлемым. но для mergeSort, почему первый запуск занял гораздо больше времени, чем позже? 37мс: 4мс для OptimizedMergeSort.

Я думаю, что даже если реализация Optimized / MergeSort была неправильной, выходные данные должны оставаться прежними, почему когда-то один и тот же вызов метода занимает больше времени, иногда более короткое время?

Как информация, все эти классы * Sort расширяют супер-абстрактный класс Sorting. у него есть абстрактный метод void sort(int[] data)

MergeSort имеет метод mergeSorting и метод merge (). OptimizedMergeSort расширяет MergeSort и переопределяет метод mergeSorting() (поскольку при размере массива <= 7 он будет выполнять inserttionSort) и повторно использует метод <code>merge() из класса MergeSort.

Спасибо за чтение этого длинного текста и кодов. Я ценю, если вы, ребята, можете дать мне некоторые объяснения.

Все тесты проводились в Eclipse под Linux.

Ответы [ 3 ]

3 голосов
/ 03 декабря 2011

Микропроцессорный Java-код, как известно, хитрый.

Весьма вероятно, что в какой-то момент включится компилятор Just-in-Time, чтобы скомпилировать ваш байт-код Java в машинный код.Каждая компиляция требует времени, но результирующий код, скорее всего, будет работать быстрее.

Есть и другие подводные камни, но я думаю, что вышеупомянутое наиболее вероятно в вашем случае.

Следующий ответ SOочень хорошее чтение: https://stackoverflow.com/a/513259/367273

1 голос
/ 03 декабря 2011

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

В первом случае,у вас есть какая-то другая программа, потребляющая физическую память, которая заставляет вашу программу чаще выполнять пейджинг и / или сбор мусора.

Во-вторых, какая-то другая программа (если вы используете Mac, я бы поспорилMachine) периодически работает, потребляя процессор.

В любом случае, вы можете узнать, как начать использовать инструменты, поставляемые с Java.jconsole и VirtualVM - это хорошие возможности.

Как правило, при возникновении проблемы с производительностью необходимо выполнить три ключевых шага: измерение, измерение и ИЗМЕРЕНИЕ.

Обновление

Я согласен, что JIT может иметь значение, но у меня сложилось впечатление, что это отдельные прогоны;вы вызываете новую JVM каждый раз.Это минимизирует эффект JIT.

0 голосов
/ 03 декабря 2011

Чтобы уменьшить эффект JIT, вы можете отменить несколько начальных прогонов для каждого теста. Также для более точных результатов в среднем более 100 или 1000 прогонов.

Вы также можете выполнить System.gc () и Thread.yeild () перед началом измерений. Если вы собираетесь тестировать только несколько итераций, предпочтите system.nanoTime () и не используйте system.currentTimeMillis (это недостаточно точно).

...