¿Existe una diferencia de rendimiento entre un bucle for y un bucle for-each?

184

¿Cuál es, si la hay, la diferencia de rendimiento entre los siguientes dos bucles?

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

y

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
eflles
fuente
@Keparo: Es un ciclo "para cada" no un ciclo "for-in"
Ande Turner
en java se llama "para cada uno", pero cuando se trata del objetivo C se llama "for In" loop.
DamithH
El rendimiento extendido para el bucle se trata aquí: stackoverflow.com/questions/12155987/…
eckes
Incluso si hubiera una diferencia, sería tan menor que debería favorecer la legibilidad a menos que este código se ejecute billones de veces por segundo. Y de todos modos necesitaría un punto de referencia adecuado para evitar la optimización prematura.
Zabuzard

Respuestas:

212

Del artículo 46 en Java efectivo por Joshua Bloch:

El ciclo for-each, introducido en la versión 1.5, elimina el desorden y la posibilidad de error al ocultar completamente el iterador o la variable de índice. El idioma resultante se aplica igualmente a colecciones y matrices:

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

Cuando vea los dos puntos (:), léalo como "en". Por lo tanto, el bucle anterior se lee como "para cada elemento e en elementos". Tenga en cuenta que no hay penalización de rendimiento por usar el bucle for-each, incluso para matrices. De hecho, puede ofrecer una ligera ventaja de rendimiento sobre un ciclo for normal en algunas circunstancias, ya que calcula el límite del índice de matriz solo una vez. Si bien puede hacerlo a mano (Artículo 45), los programadores no siempre lo hacen.

Vijay Dev
fuente
48
Cabe mencionar que en una de cada bucle-no hay manera de acceder a un contador de índice (ya que no existe)
basszero
Sí, pero ese contador ahora es visible fuera del bucle. Claro, es una solución simple, pero también lo es para cada uno.
Interno
74
Existe la penalización de rendimiento de asignar el iterador. Tenía un código altamente paralelo en un fondo de pantalla animado de Android. Vi que el recolector de basura se estaba volviendo loco. Fue porque los bucles for-each estaban asignando iteradores temporales en muchos subprocesos diferentes (de corta duración), porque el recolector de basura trabajaba mucho. Cambiar a bucles basados ​​en índices regulares solucionó el problema.
gsingh2011
1
@ gsingh2011 Pero esto también depende de si está utilizando una lista de acceso aleatorio o no. Supongo que usar el acceso basado en índices a listas de acceso no aleatorio será mucho peor que usar para cada uno con listas de acceso aleatorio. Si está trabajando con la Lista de interfaces y, por lo tanto, no conoce el tipo de implementación real, puede verificar si la lista es una instancia de (implementa) RandomAccess, si realmente le importa tanto: docs.oracle.com/javase/8 /docs/api/java/util/RandomAccess.html
Puce
3
@ gsingh2011 Los documentos de Android ( developer.android.com/training/articles/perf-tips.html#Loops ) mencionan que tendrá una penalización de rendimiento cuando use foreach solo en ArrayList, no en otras colecciones. Tengo curiosidad por saber si ese fue tu caso.
Viccari
29

Todos estos bucles hacen exactamente lo mismo, solo quiero mostrarlos antes de tirar mis dos centavos.

Primero, la forma clásica de recorrer List:

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

En segundo lugar, la forma preferida, ya que es menos propenso a errores (¿cuántas veces USTEDES ha hecho "oops, mezclado las variables i y j en estos bucles dentro de los bucles"?).

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

En tercer lugar, el micro optimizado para el bucle:

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

Ahora los dos centavos reales: al menos cuando estaba probando estos, el tercero fue el más rápido al contar milisegundos sobre cuánto tiempo tomó para cada tipo de bucle con una operación simple que se repitió unos pocos millones de veces: esto estaba usando Java 5 con jre1.6u10 en Windows en caso de que alguien esté interesado.

Si bien al menos parece ser que el tercero es el más rápido, realmente debería preguntarse si quiere correr el riesgo de implementar esta optimización de mirilla en todas partes en su código de bucle, ya que, por lo que he visto, el bucle real no es Por lo general, es la parte más lenta de cualquier programa real (o tal vez solo estoy trabajando en el campo equivocado, quién sabe). Y también, como mencioné en el pretexto para el bucle for-each de Java (algunos se refieren a él como bucle Iterator y otros como bucle for-in ), es menos probable que golpee ese error estúpido en particular al usarlo. Y antes de debatir cómo esto incluso puede ser incluso más rápido que los otros, recuerde que javac no optimiza en absoluto el código de bytes (bueno, casi de todos modos), solo lo compila.

Sin embargo, si está interesado en la microoptimización y / o su software utiliza muchos bucles recursivos, puede estar interesado en el tercer tipo de bucle. Solo recuerde comparar su software bien antes y después de cambiar los bucles for que tiene para este extraño y micro optimizado.

P Arrayah
fuente
55
Tenga en cuenta que el ciclo for con ++ i <= size está "basado en 1", por ejemplo, el método get dentro del ciclo se llamará para los valores 1, 2, 3, etc.
volley
15
Una mejor manera de escribir el bucle micro optimizado es para (int i = 0, size = strings.size (); ++ i <= size;) {} Esto es preferible porque minimiza el alcance del tamaño
Dónal
1
el tercero no comienza desde i = 1 la primera vez que pasa por el ciclo, omitiendo el primer elemento. y ser un bucle for es innecesario. int n = strings.length; while (n -> 0) {System.out.println ("" + n + "" + cadenas [n]); }
Lassi Kinnunen
1
@ Dónal ese patrón de bucle pierde el primero y da un IOOBE. Este funciona: for (int i = -1, size = list.size (); ++ i <size;)
Nathan Adams
1
"Todos estos bucles hacen exactamente lo mismo" es incorrecto. Uno usa acceso aleatorio get(int), el otro usa un Iterator. Considere LinkedListdónde el rendimiento de for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }es mucho peor, ya que lo está haciendo get(int)n veces.
Steve Kuo
13

El ciclo for-each generalmente debería preferirse. El enfoque "get" puede ser más lento si la implementación de la Lista que está utilizando no admite acceso aleatorio. Por ejemplo, si se usa una LinkedList, incurriría en un costo transversal, mientras que el enfoque para cada usa un iterador que realiza un seguimiento de su posición en la lista. Más información sobre los matices del ciclo for-each .

Creo que el artículo ya está aquí: nueva ubicación

El enlace que se muestra aquí estaba muerto.

Zach Scrivena
fuente
12

Bueno, el impacto en el rendimiento es en su mayoría insignificante, pero no es cero. Si nos fijamos en JavaDoc de la RandomAccessinterfaz:

Como regla general, una implementación de Lista debería implementar esta interfaz si, para instancias típicas de la clase, este bucle:

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

corre más rápido que este bucle:

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

Y for-each loop está usando la versión con iterador, ArrayListpor ejemplo, for-each loop no es más rápido.

Sarmun
fuente
Realmente cada uno? ¿Incluso uno con una matriz? Leí aquí stackoverflow.com/questions/1006395/… que no involucra un iterador.
Ondra Žižka
@ OndraŽižka: El ciclo for-each usa iteradores cuando se recorre Iterables, pero no matrices. Para las matrices se usa un bucle for con una variable de índice. Hay información sobre esto en el JLS .
Lii
sí, entonces primero necesitarías crearlo en una matriz con toArray, teniendo un costo.
Lassi Kinnunen
6

Parece haber una diferencia desafortunadamente.

Si observa el código de bytes generado para ambos tipos de bucles, son diferentes.

Aquí hay un ejemplo del código fuente Log4j.

En /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java tenemos una clase interna estática llamada Log4jMarker que define:

    /*
     * 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;
    }

Con bucle estándar:

  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

Con para cada uno:

  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

¿Qué pasa con ESO Oracle?

He intentado esto con Java 7 y 8 en Windows 7.

Gary Gregory
fuente
77
Para aquellos que intentan leer el desensamblaje, el resultado neto es que el código generado dentro del bucle es idéntico, pero la configuración para cada parece haber creado una variable temporal adicional que contiene una referencia al segundo argumento. Si la variable adicional oculta se registra, pero el parámetro en sí no se encuentra durante la generación del código, entonces el para-cada sería más rápido; si el parámetro se registra en el ejemplo for (;;), el tiempo de ejecución sería idéntico. ¿Tienes un punto de referencia?
Robin Davies
4

Siempre es mejor usar el iterador en lugar de indexar. Esto se debe a que el iterador probablemente esté optimizado para la implementación de la Lista mientras que indexado (llamando a get) podría no estarlo. Por ejemplo, LinkedList es una Lista, pero la indexación a través de sus elementos será más lenta que iterar usando el iterador.

Steve Kuo
fuente
10
Creo que no hay tal cosa como "siempre" en las optimizaciones de rendimiento.)
eckes
4

foreach aclara la intención de su código y eso normalmente se prefiere a una mejora de velocidad muy pequeña, si la hay.

Cada vez que veo un bucle indexado, tengo que analizarlo un poco más para asegurarme de que hace lo que creo que hace, por ejemplo, ¿Comienza desde cero, incluye o excluye el punto final, etc.?

Parece que la mayor parte del tiempo la paso leyendo el código (que escribí o alguien más escribió) y la claridad es casi siempre más importante que el rendimiento. Es fácil descartar el rendimiento en estos días porque Hotspot hace un trabajo increíble.

Fortyrunner
fuente
4

El siguiente código:

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

Da el siguiente resultado en mi sistema:

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

Estoy ejecutando Ubuntu 12.10 alpha con OracleJDK 1.7 actualización 6.

En general, HotSpot optimiza muchas indirecciones y simples operaciones redundantes, por lo que en general no debe preocuparse por ellas a menos que haya muchas de ellas en secuencia o estén muy anidadas.

Por otro lado, la entrada indexada en LinkedList es mucho más lenta que llamar al siguiente en el iterador para LinkedList, por lo que puede evitar ese impacto en el rendimiento mientras mantiene la legibilidad cuando usa iteradores (explícita o implícitamente en cada ciclo).

Wibowit
fuente
3

Incluso con algo como un ArrayList o un Vector, donde "get" es una simple búsqueda de matriz, el segundo bucle todavía tiene una sobrecarga adicional que el primero no. Esperaría que fuera un poco más lento que el primero.

Paul Tomblin
fuente
El primer ciclo también tiene que obtener cada elemento. Crea un iterador detrás de escena para hacer esto. Son realmente equivalentes.
Bill the Lizard el
Pensando en términos de C, un iterador solo puede incrementar un puntero, pero un get tendría que multiplicar el valor de i por el ancho de un puntero cada vez.
Paul Tomblin el
Depende del tipo de lista que use. Sin embargo, creo que tienes razón, usar get nunca sería más rápido y, a veces, más lento.
Bill the Lizard el
3

La única forma de saberlo con certeza es compararlo, e incluso eso no es tan simple como puede parecer . El compilador JIT puede hacer cosas muy inesperadas a su código.

Jouni K. Seppänen
fuente
3

Aquí hay un breve análisis de la diferencia presentada por el equipo de desarrollo de Android:

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

El resultado es que no es una diferencia, y en entornos muy restringidos con listas muy grandes podría ser una diferencia notable. En sus pruebas, el tiempo de cada ciclo tomó el doble. Sin embargo, su prueba se realizó sobre una lista de 400,000 enteros. La diferencia real por elemento en la matriz fue de 6 microsegundos . No lo he probado y no dijeron, pero esperaría que la diferencia sea un poco más grande usando objetos en lugar de primitivos, pero aún así, a menos que esté construyendo código de biblioteca donde no tenga idea de la escala de lo que se le preguntará para repetir, creo que no vale la pena destacar la diferencia.

TBridges42
fuente
2

Por el nombre de la variable objectArrayList, supongo que es una instancia dejava.util.ArrayList . En ese caso, la diferencia de rendimiento sería imperceptible.

Por otro lado, si se trata de una instancia de java.util.LinkedList, el segundo enfoque será mucho más lento como elList#get(int) es una operación O (n).

Por lo tanto, siempre se prefiere el primer enfoque a menos que la lógica en el ciclo necesite el índice.

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

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

Ambos hacen lo mismo, pero para un uso de programación fácil y seguro para cada uno, hay posibilidades de errores propensos en la segunda forma de uso.

Santosh
fuente
1

Es extraño que nadie haya mencionado lo obvio: foreach asigna memoria (en forma de iterador), mientras que un bucle normal no asigna ninguna memoria. Para los juegos en Android, esto es un problema, porque significa que el recolector de basura se ejecutará periódicamente. En un juego no quieres que el recolector de basura se ejecute ... NUNCA. Por lo tanto, no use bucles foreach en su método de dibujo (o render).

neural
fuente
1

La respuesta aceptada responde a la pregunta, aparte del caso excepcional de ArrayList ...

Como la mayoría de los desarrolladores confían en ArrayList (al menos eso creo)

Así que estoy obligado a agregar la respuesta correcta aquí.

Directamente de la documentación del desarrollador: -

El bucle for mejorado (también conocido a veces como bucle "for-each") se puede usar para colecciones que implementan la interfaz Iterable y para matrices. Con las colecciones, se asigna un iterador para realizar llamadas de interfaz a hasNext () y next (). Con una ArrayList, un bucle contado escrito a mano es aproximadamente 3 veces más rápido (con o sin JIT), pero para otras colecciones, la sintaxis mejorada para el bucle será exactamente equivalente al uso explícito del iterador.

Hay varias alternativas para iterar a través de una matriz:

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 () es el más lento, porque el JIT aún no puede optimizar el costo de obtener la longitud de la matriz una vez por cada iteración a través del ciclo.

uno () es más rápido. Saca todo a las variables locales, evitando las búsquedas. Solo la longitud de la matriz ofrece un beneficio de rendimiento.

two () es más rápido para dispositivos sin un JIT e indistinguible de one () para dispositivos con un JIT. Utiliza la sintaxis mejorada para bucles introducida en la versión 1.5 del lenguaje de programación Java.

Por lo tanto, debe usar el bucle for mejorado de forma predeterminada, pero considere un bucle contado escrito a mano para la iteración ArrayList crítica para el rendimiento.

Sreekanth Karumanaghat
fuente
-2

Sí, la for-eachvariante es más rápida de lo normal.index-based-for-loop .

for-eachusos variantes iterator. Por lo tanto, el desplazamiento es más rápido que el forbucle normal, que se basa en índices.
Esto se debe a que iteratorestán optimizados para atravesar, porque apunta justo antes del siguiente elemento y justo después del elemento anterior . Una de las razones para index-based-for-loopser lento es que tiene que calcular y moverse a la posición del elemento cada vez que no está con el iterator.

cse
fuente
-3
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+" ");
        }
    }
}
SS Tiwari
fuente