Есть ли разница в производительности между циклом for и циклом for-each? - PullRequest
169 голосов
/ 02 ноября 2008

Какова разница в производительности между следующими двумя циклами, если таковая имеется?

for (Object o: objectArrayList) {
    o.DoSomething();
}

и

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}

Ответы [ 17 ]

195 голосов
/ 02 ноября 2008

Из пункта 46 в Эффективная Java Джошуа Блоха:

Петля for-each, представленная в релиз 1.5, избавляется от беспорядка и возможность для ошибки скрытие итератора или индексной переменной полностью. В результате идиома относится в равной степени к коллекциям и Массивы:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

Когда вы видите двоеточие (:), читайте его как «В». Таким образом, цикл выше читается как «Для каждого элемента е в элементах». Примечание что нет потери производительности для использования для каждого цикла, даже для массивы. На самом деле, он может предложить небольшое преимущество в производительности по сравнению с обычным для цикла в некоторых обстоятельствах, как это вычисляет предел индекса массива только однажды. Хотя вы можете сделать это рука (пункт 45), программисты не всегда делай так.

27 голосов
/ 02 ноября 2008

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

Во-первых, классический способ просмотра списка List:

for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }

Во-вторых, предпочтительный способ, поскольку он менее подвержен ошибкам (сколько раз вы делали «упс, смешали переменные i и j в этих циклах внутри циклов»?).

for(String s : strings) { /* do something using s */ }

В-третьих, микро-оптимизированный для цикла:

int size = strings.size();
for(int i=0;++i<=size;) { /* do something using strings.get(i) */ }

Теперь фактические два цента: по крайней мере, когда я тестировал их, третий был самым быстрым при подсчете миллисекунд на то, сколько времени потребовалось для каждого типа цикла, когда простая операция повторялась несколько миллионов раз - это было использование Java 5 с jre1.6u10 в Windows на случай, если кому-то будет интересно.

Хотя, по крайней мере, кажется, что третий является самым быстрым, вы действительно должны спросить себя, хотите ли вы рискнуть реализовать эту оптимизацию глазка во всем циклическом коде, поскольку из того, что я видел, на самом деле зацикливание обычно не самая трудоемкая часть любой реальной программы (или, может быть, я просто работаю не в той области, кто знает). И также, как я уже упоминал в предлете для цикла Java for-each (некоторые называют его цикл итератора , а другие - цикл for-in ), вы менее вероятно, что попадет в одну конкретную глупую ошибку при ее использовании. И прежде чем обсуждать, как это может быть даже быстрее, чем другие, помните, что javac вообще не оптимизирует байт-код (ну, в общем, почти вообще), он просто компилирует его.

Если вы занимаетесь микрооптимизацией, и / или ваше программное обеспечение использует множество рекурсивных циклов, то вас может заинтересовать третий тип цикла. Просто не забывайте тестировать свое программное обеспечение как до, так и после замены циклов for на этот странный микрооптимизированный.

12 голосов
/ 02 ноября 2008

Цикл for-each обычно должен быть предпочтительным. Подход «get» может быть медленнее, если используемая реализация List не поддерживает произвольный доступ. Например, если используется LinkedList, вы понесете издержки обхода, тогда как подход для каждого использует итератор, который отслеживает его положение в списке. Подробнее о нюансах цикла for-each .

Я думаю, что статья сейчас здесь: новое местоположение

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

10 голосов
/ 24 февраля 2009

Что ж, влияние на производительность в основном незначительное, но не нулевое. Если вы посмотрите на JavaDoc интерфейса RandomAccess:

Как правило, список реализация должна реализовать это интерфейс, если для типичных случаев класс, этот цикл:

for (int i=0, n=list.size(); i < n; i++)
    list.get(i);

работает быстрее, чем этот цикл:

for (Iterator i=list.iterator(); i.hasNext();)
      i.next();

И цикл for-each использует версию с итератором, поэтому, например, для ArrayList цикл for-each не самый быстрый.

5 голосов
/ 27 сентября 2014

Кажется, что есть разница, к сожалению.

Если посмотреть на сгенерированный байтовый код для обоих типов циклов, они будут разными.

Вот пример из исходного кода Log4j.

В /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java у нас есть статический внутренний класс с именем Log4jMarker, который определяет:

    /*
     * Called from add while synchronized.
     */
    private static boolean contains(final Marker parent, final Marker... localParents) {
        //noinspection ForLoopReplaceableByForEach
        for (final Marker marker : localParents) {
            if (marker == parent) {
                return true;
            }
        }
        return false;
    }

со стандартной петлей:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: iconst_0
       1: istore_2
       2: aload_1
       3: arraylength
       4: istore_3
       5: iload_2
       6: iload_3
       7: if_icmpge     29
      10: aload_1
      11: iload_2
      12: aaload
      13: astore        4
      15: aload         4
      17: aload_0
      18: if_acmpne     23
      21: iconst_1
      22: ireturn
      23: iinc          2, 1
      26: goto          5
      29: iconst_0
      30: ireturn

С для каждого:

  private static boolean contains(org.apache.logging.log4j.Marker, org.apache.logging.log4j.Marker...);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: arraylength
       4: istore_3
       5: iconst_0
       6: istore        4
       8: iload         4
      10: iload_3
      11: if_icmpge     34
      14: aload_2
      15: iload         4
      17: aaload
      18: astore        5
      20: aload         5
      22: aload_0
      23: if_acmpne     28
      26: iconst_1
      27: ireturn
      28: iinc          4, 1
      31: goto          8
      34: iconst_0
      35: ireturn

Что случилось с этим Oracle?

Я пробовал это с Java 7 и 8 на Windows 7.

4 голосов
/ 03 ноября 2008

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

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

Большая часть моего времени, кажется, тратится на чтение кода (который я написал или кто-то другой написал), и ясность почти всегда важнее производительности. В наши дни легко отмахнуться от производительности, потому что Hotspot делает такую ​​потрясающую работу.

4 голосов
/ 03 ноября 2008

Всегда лучше использовать итератор вместо индексации. Это связано с тем, что итератор, скорее всего, оптимизирован для реализации List, а индексированный (вызывающий get) может и не быть. Например, LinkedList является списком, но индексирование по его элементам будет выполняться медленнее, чем итерация с использованием итератора.

4 голосов
/ 29 августа 2012

следующий код:

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

interface Function<T> {
    long perform(T parameter, long x);
}

class MyArray<T> {

    T[] array;
    long x;

    public MyArray(int size, Class<T> type, long x) {
        array = (T[]) Array.newInstance(type, size);
        this.x = x;
    }

    public void forEach(Function<T> function) {
        for (T element : array) {
            x = function.perform(element, x);
        }
    }
}

class Compute {
    int factor;
    final long constant;

    public Compute(int factor, long constant) {
        this.factor = factor;
        this.constant = constant;
    }

    public long compute(long parameter, long x) {
        return x * factor + parameter + constant;
    }
}

public class Main {

    public static void main(String[] args) {
        List<Long> numbers = new ArrayList<Long>(50000000);
        for (int i = 0; i < 50000000; i++) {
            numbers.add(i * i + 5L);
        }

        long x = 234553523525L;

        long time = System.currentTimeMillis();
        for (int i = 0; i < numbers.size(); i++) {
            x += x * 7 + numbers.get(i) + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        time = System.currentTimeMillis();
        for (long i : numbers) {
            x += x * 7 + i + 3;
        }
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(x);
        x = 0;
        numbers = null;
        MyArray<Long> myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return x * 8 + parameter + 5L;
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
        myArray = null;
        myArray = new MyArray<Long>(50000000, Long.class, 234553523525L);
        for (int i = 0; i < 50000000; i++) {
            myArray.array[i] = i * i + 3L;
        }
        time = System.currentTimeMillis();
        myArray.forEach(new Function<Long>() {

            public long perform(Long parameter, long x) {
                return new Compute(8, 5).compute(parameter, x);
            }
        });
        System.out.println(System.currentTimeMillis() - time);
        System.out.println(myArray.x);
    }
}

Дает следующий вывод в моей системе:

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

Я использую Ubuntu 12.10 alpha с OracleJDK 1.7, обновление 6.

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

С другой стороны, индексированное получение в LinkedList намного медленнее, чем вызов следующего для итератора для LinkedList, поэтому вы можете избежать такого снижения производительности, сохраняя читабельность при использовании итераторов (явно или неявно в цикле for-each).

3 голосов
/ 02 июля 2016

Вот краткий анализ разницы, представленной командой разработчиков Android:

https://www.youtube.com/watch?v=MZOf3pOAM6A

В результате является разницей, и в очень ограниченных средах с очень большими списками это может быть заметной разницей. В их тестировании для каждого цикла потребовалось вдвое больше времени. Тем не менее, их тестирование составило более 400 000 целых чисел. Фактическая разница на элемент в массиве составила 6 микросекунд . Я не проверял, и они не сказали, но я ожидаю, что разница будет немного больше, используя объекты, а не примитивы, но даже если вы не создаете библиотечный код, где вы не знаете масштаб того, что вас спросят итерации, я думаю, что разница не стоит подчеркивать.

3 голосов
/ 02 ноября 2008

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...