Современный для цикла для примитивного массива

Есть ли разница в производительности между циклами for в примитивном массиве?

Предположим:

double[] doubleArray = new double[300000];


for (double var: doubleArray) 
   someComplexCalculation(var);

или:

for ( int i = 0, y = doubleArray.length; i < y; i++)
   someComplexCalculation(doubleArray[i]);

Результат теста

Я действительно профилировал его:

Total timeused for modern loop= 13269ms
Total timeused for old loop   = 15370ms

Таким образом, современный цикл работает быстрее, по крайней мере, на моем Mac OSX JVM 1.5.

Ответ 1

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

Если вы посмотрите на дизассемблированный код (скомпилированный Sun JDK 1.5), вы увидите, что "новая" форма эквивалентна следующему коду:

1: double[] tmp = doubleArray;
2: for (int i = 0, y = tmp.length; i < y; i++) {
3:   double var = tmp[i];
4:   someComplexCalculation(var);
5: }

Итак, вы можете видеть, что используются более локальные переменные. Назначение doubleArray to tmp в строке 1 является "лишним", но оно не встречается в цикле и, вероятно, не может быть измерено. Назначение var в строке 3 также является дополнительным. Если есть разница в производительности, это будет отвечать.

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

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

Ответ 2

Мое мнение таково, что вы не знаете и не должны догадываться. Попытка перехитрить компиляторов в наши дни бесплодна.

Были времена, когда люди узнавали "Шаблоны", которые, казалось, оптимизировали некоторую операцию, но в следующей версии Java эти шаблоны были на самом деле медленнее.

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

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

Как раз вовремя компиляция означает, что он может иногда превосходить C, и нет причин, по которым он не может опережать статический язык ассемблера в некоторых случаях (сборка не может заранее определить, что вызов не требуется, Java может время от времени просто это.

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

Ответ 3

Почему бы не измерить его самостоятельно?

Это звучит немного жестко, но такие вопросы очень легко проверить.

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

Ответ 4

Нет никакой разницы. Java преобразует расширенное значение в обычный цикл. Улучшенный для всего лишь "синтаксический сахар". Генерируемый байт-код является одинаковым для обеих циклов.

Ответ 5

Мне очень понравился ваш вопрос, даже после моего предыдущего ответа. Поэтому я тоже решил проверить это. Я написал эту небольшую часть кода (пожалуйста, игнорируйте правильность математики о проверке, является ли число простым; -)):

public class TestEnhancedFor {

    public static void main(String args[]){
        new TestEnhancedFor();
    }

    public TestEnhancedFor(){
        int numberOfItems = 100000;
        double[] items = getArrayOfItems(numberOfItems);
        int repetitions = 0;
        long start, end;

        do {
            start = System.currentTimeMillis();
            doNormalFor(items);
            end = System.currentTimeMillis();
            System.out.printf("Normal For. Repetition %d: %d\n", 
                    repetitions, end-start);

            start = System.currentTimeMillis();
            doEnhancedFor(items);
            end = System.currentTimeMillis();
            System.out.printf("Enhanced For. Repetition %d: %d\n\n", 
                    repetitions, end-start);

        } while (++repetitions < 5);
    }

    private double[] getArrayOfItems(int numberOfItems){
        double[] items = new double[numberOfItems];
        for (int i=0; i < numberOfItems; i++)
            items[i] = i;
        return items;
    }

    private void doSomeComplexCalculation(double item){
        // check if item is prime number
        for (int i = 3; i < item / 2; i+=2){
            if ((item / i) == (int) (item / i)) break;
        }
    }

    private void doNormalFor(double[] items){
        for (int i = 0; i < items.length; i++)
            doSomeComplexCalculation(items[i]);
    }

    private void doEnhancedFor(double[] items){
        for (double item : items)
            doSomeComplexCalculation(item);
    }

}

Запуск приложения дал мне следующие результаты:

Нормальный для. Повторение 0: 5594 Улучшенный для. Повторение 0: 5594

Нормальный для. Повторение 1: 5531 Улучшенный для. Повторение 1: 5547

Нормальный для. Повторение 2: 5532 Улучшенный для. Повторение 2: 5578

Нормальный для. Повторение 3: 5531 Улучшенный для. Повторение 3: 5531

Нормальный для. Повторение 4: 5547 Улучшенный для. Повторение 4: 5532

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

Среднее время (исключая первое повторение) составляет 5535,25мс для нормального цикла и 5547 мс для расширенного цикла. Но мы можем видеть, что наилучшее время работы для обеих петель одинаково (5531 мс), поэтому я думаю, что мы можем прийти к выводу, что обе петли имеют одинаковую производительность - и изменения пройденного времени обусловлены другими приложениями (даже ОС) машины.