Pregunta

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

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

y

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
¿Fue útil?

Solución

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

  

El bucle for-each, introducido en   versión 1.5, se deshace del desorden   y la oportunidad de error por   ocultando el iterador o variable de índice   completamente. El idioma resultante   Se aplica igualmente a las colecciones y   matrices:

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

Cuando veas los dos puntos (:), léelo como   "En". Por lo tanto, el bucle anterior se lee como   “Para cada elemento e en elementos”. Nota   que no hay penalización de rendimiento   para usar el bucle for-each, incluso para   matrices De hecho, puede ofrecer una ligera   ventaja de rendimiento sobre un ordinario   para bucle en algunas circunstancias, ya que   calcula el límite del índice de matriz   sólo una vez. Mientras que usted puede hacer esto por   mano (artículo 45), los programadores no   siempre hazlo.

Otros consejos

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

Primero, la forma clásica de recorrer la lista:

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 has hecho el " oops, mezclado las variables i y j en estos bucles dentro de los bucles " cosa?).

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

Tercero, el micro-optimizado para bucle:

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

Ahora los dos centavos reales: al menos cuando los estaba probando, el tercero fue el más rápido al contar milisegundos sobre el tiempo que tomó cada tipo de bucle con una simple operación que se repitió unos millones de veces. Esto fue utilizando 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 todo su código de bucle ya que, por lo que he visto, real Loops no suele ser la parte que más tiempo consume de cualquier programa real (o quizás solo estoy trabajando en el campo equivocado, quién sabe) Y también como mencioné en el pretexto para el for-each loop de Java (algunos se refieren a él como Iterator loop y otros como for-in loop ) es menos probable que golpees ese error estúpido en particular cuando lo usas. Y antes de debatir cómo esto puede ser incluso más rápido que los otros, recuerde que javac no optimiza el código de bytes (bueno, casi de todos modos), simplemente lo compila.

Si estás en la micro-optimización y / o tu software usa muchos bucles recursivos y tal vez estés interesado en el tercer tipo de bucle. Solo recuerde comparar bien su software antes y después de cambiar los bucles for que tiene a este extraño y micro optimizado.

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

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

El enlace que se muestra aquí estaba muerto.

Bueno, el impacto en el rendimiento es mayormente insignificante, pero no es cero. Si nos fijamos en JavaDoc de la interfaz de RandomAccess :

  

Como regla general, una lista   implementación debe implementar este   interfaz si, para casos típicos de   la clase, este bucle:

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

se ejecuta más rápido que este bucle:

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

Y para cada bucle se usa la versión con iterador, así que para ArrayList , por ejemplo, para cada bucle no es más rápido.

Desafortunadamente, parece haber una diferencia.

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

Aquí hay un ejemplo del código fuente de 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 ESE Oracle?

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

Siempre es mejor usar el iterador en lugar de la indexación. Esto se debe a que lo más probable es que el iterador esté optimizado para la implementación de la Lista, mientras que el indexado (llamada 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 la iteración usando el iterador.

foreach hace que la intención de su código sea más clara y que 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 haga lo que creo que hace, por ejemplo. ¿Comienza desde cero, incluye o excluye el punto final, etc.?

La mayor parte de mi tiempo parece estar dedicado a leer 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.

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 una gran cantidad de direccionamientos indirectos y operaciones redundantes simples, por lo que, en general, no debe preocuparse por ellos a menos que haya muchos en secuencia o estén muy anidados.

Por otra parte, la obtención 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 al tiempo que conserva la legibilidad cuando usa iteradores (explícita o implícitamente en cada bucle).

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

La única forma de saberlo con seguridad es compararlo, e incluso esa es no tan simple como suena . El compilador JIT puede hacer cosas muy inesperadas para su código.

A continuación, presentamos un breve análisis de la diferencia que presenta el equipo de desarrollo de Android:

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

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

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

Por otra parte, si es una instancia de java.util.LinkedList , el segundo enfoque será mucho más lento ya que List # get (int) es un Operación O (n).

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

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 que exista un error en la segunda forma de uso.

Es extraño que nadie haya mencionado lo obvio: cada uno asigna memoria (en forma de iterador), mientras que un bucle normal no asigna 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. Así que no uses bucles foreach en tu método de dibujo (o render).

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

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

Por lo tanto, estoy obligado a agregar la respuesta correcta aquí.

Directamente de la documentación del desarrollador: -

El bucle for mejorado (también conocido como bucle para cada ciclo) se puede usar para colecciones que implementan la interfaz de Iterable y para arreglos. Con las colecciones, se asigna un iterador para realizar llamadas de interfaz a hasNext () y next (). Con un 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;
    }
}

cero () es el más lento, porque el JIT aún no puede optimizar el costo de obtener la longitud del arreglo una vez por cada iteración a través del bucle.

uno () es más rápido. Se saca todo en 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, y no se puede distinguir de uno () para dispositivos con un JIT. Utiliza la sintaxis de bucle mejorada 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 de conteo escrito a mano para la iteración de ArrayList de rendimiento crítico.

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

Sí, la variante de para-cada es más rápida que la normal index-based-for-loop .

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top