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

StackOverflow https://stackoverflow.com/questions/256859

  •  05-07-2019
  •  | 
  •  

Вопрос

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

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

и

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
Это было полезно?

Решение

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

  

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

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

Когда вы видите двоеточие (:), читайте его как   & # 8220; в & # 8221. Таким образом, цикл выше читается как   & # 8220; для каждого элемента e в элементах. & # 8221; Заметка   что нет потери производительности   для использования для каждого цикла, даже для   массивы. На самом деле, он может предложить небольшое   преимущество в производительности по сравнению с обычным   для цикла в некоторых обстоятельствах, как это   вычисляет предел индекса массива   только однажды. Хотя вы можете сделать это   стороны (пункт 45), программисты не & # 8217; т   всегда делай так.

Другие советы

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

Во-первых, классический способ просмотра списка 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 на этот странный, микрооптимизированный.

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

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

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

Что ж, влияние на производительность в основном незначительно, но не равно нулю. Если вы посмотрите на 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 не самый быстрый.

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

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

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

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

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

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

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

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

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 update 6.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Существует несколько альтернатив для перебора массива:

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 еще не может оптимизировать стоимость получения длины массива один раз для каждой итерации цикла.

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

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

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

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+" ");
        }
    }
}

Да, вариант для каждого быстрее, чем обычно на основе индекса для цикла . Вариант

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top