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

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

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

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

и

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

Ответ 1

Из пункта 46 " Эффективной Явы " Джошуа Блоха:

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

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

Когда вы видите двоеточие (:), читайте его как "в". Таким образом, цикл выше читается как "для каждого элемента e в элементах". Обратите внимание, что нет никакого снижения производительности при использовании цикла for-each, даже для массивов. Фактически, он может предложить небольшое преимущество в производительности по сравнению с обычным циклом for в некоторых случаях, поскольку он вычисляет ограничение индекса массива только один раз. Хотя вы можете сделать это вручную (пункт 45), программисты не всегда делают это.

Ответ 2

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

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

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

Во-вторых, предпочтительный способ, поскольку он меньше подвержен ошибкам (сколько раз вы делали "oops, смешали переменные я и 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, если кто-то заинтересован.

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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

Вот пример из исходного кода 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.

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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 намного медленнее, чем вызов next on iterator для LinkedList, поэтому вы можете избежать этого повышения производительности, сохраняя удобочитаемость при использовании итераторов (явно или неявно в каждом цикле).

Ответ 9

Даже с чем-то вроде ArrayList или Vector, где "get" - это простой поиск массива, во втором цикле все еще есть дополнительные накладные расходы, которые первый нет. Я ожидаю, что это будет немного медленнее, чем первый.

Ответ 10

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

Ответ 11

Ниже приведен краткий анализ различий, выработанных командой разработчиков Android:

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

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

Ответ 12

По имени переменной objectArrayList, я предполагаю, что это экземпляр java.util.ArrayList. В этом случае разница в производительности будет незаметной.

С другой стороны, если это экземпляр java.util.LinkedList, второй подход будет намного медленнее, так как List#get(int) является операцией O (n).

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

Ответ 13

1. for(Object o: objectArrayList){
    o.DoSomthing();
}
and

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

Оба делают то же самое, но для простого и безопасного программирования, используемого для каждого, есть вероятность подверженности ошибкам во втором способе использования.

Ответ 14

Странно, что никто не упомянул очевидное - foreach выделяет память (в виде итератора), тогда как обычный цикл for не выделяет никакой памяти. Для игр на Android это проблема, потому что это означает, что сборщик мусора будет запускаться периодически. В игре вы не хотите, чтобы сборщик мусора работал... КОГДА-ЛИБО. Поэтому не используйте циклы foreach в методе рисования (или рендеринга).

Ответ 15

Принятый ответ отвечает на вопрос, кроме исключительного случая ArrayList...

Поскольку большинство разработчиков полагаются на ArrayList (по крайней мере, я так считаю)

Поэтому я обязан добавить правильный ответ здесь.

Прямо из документации разработчика: -

Усовершенствованный цикл (также иногда называемый циклом for-each) может использоваться для коллекций, реализующих интерфейс Iterable и для массивов. С коллекциями итератор выделяется для вызова интерфейса hasNext() и next(). С ArrayList ручная подсчитанная петля примерно в 3 раза быстрее (с или без JIT), но для других коллекций усиленный синтаксис цикла будет в точности эквивалентен явному использованию итератора.

Существует несколько альтернатив для итерации через массив:

static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

zero() является самым медленным, поскольку JIT еще не может оптимизировать стоимость получения длины массива один раз для каждой итерации через цикл.

один() быстрее. Он выводит все на локальные переменные, избегая поиска. Только длина массива дает преимущество в производительности.

two() является самым быстрым для устройств без JIT и неотличим от одного() для устройств с JIT. Он использует расширенный синтаксис цикла, введенный в версии 1.5 языка программирования Java.

Итак, вы должны использовать расширенный цикл for по умолчанию, но рассмотрите рукописный счетный цикл для критической итерации ArrayList.

Ответ 16

public class FirstJavaProgram {

    public static void main(String[] args) 
    {
        int a[]={1,2,3,45,6,6};

// Method 1: this is simple way to print array 

        for(int i=0;i<a.length;i++) 
        { 
            System.out.print(a[i]+" ");
        }

// Method 2: Enhanced For loop

        for(int i:a)
        {
            System.out.print(i+" ");
        }
    }
}

Ответ 17

Да, for-each варианта быстрее, чем обычный index-based-for-loop.

for-each варианта используется iterator. Таким образом, обход выполняется быстрее, чем обычно for цикла, основанного на индексе.
Это потому, что iterator оптимизирован для обхода, потому что он указывает непосредственно перед следующим элементом и сразу после предыдущего элемента. Одна из причин того, что index-based-for-loop на index-based-for-loop является медленным, заключается в том, что он должен вычислять и перемещаться в позицию элемента каждый раз, что не выполняется с помощью iterator.