java - loop - ¿Hay una diferencia de rendimiento entre un bucle for y un bucle for-each?
for each php (16)
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 existe una diferencia, y en entornos muy restringidos con listas muy grandes podría ser una diferencia notable. En su prueba, el bucle 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 probé y no dijeron, pero esperaría que la diferencia fuera 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 insistir en la diferencia.
¿Cuál es 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();
}
Bueno, el impacto en el rendimiento es casi insignificante, pero no es cero. Si miras a JavaDoc de la interfaz RandomAccess
:
Como regla general, una implementación de List debe implementar esta interfaz si, para instancias típicas de la clase, este ciclo:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
funciona más rápido que este bucle:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
Y para cada bucle está utilizando la versión con el iterador, por lo que para ArrayList
por ejemplo, para cada bucle no es el más rápido.
Del Item 46 en Java efectivo de Joshua Bloch:
El bucle for-each, introducido en la versión 1.5, elimina el desorden y la oportunidad de error al ocultar completamente el iterador o la variable de índice. La expresión 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 "in". Por lo tanto, el ciclo anterior se lee como "para cada elemento e en los elementos". Tenga en cuenta que no existe una penalización de rendimiento para usar el bucle for-each, incluso para matrices . De hecho, puede ofrecer una ligera ventaja de rendimiento sobre un bucle ordinario en algunas circunstancias, ya que calcula el límite del índice de matriz solo una vez. Si bien puede hacerlo a mano (Elemento 45), los programadores no siempre lo hacen.
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 la siguiente salida en mi sistema:
224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895
Estoy ejecutando Ubuntu 12.10 alpha con OracleJDK 1.7 update 6.
En general, HotSpot optimiza una gran cantidad de indirecciones y operaciones simples de reduntant, por lo que en general no debe preocuparse por ellas a menos que haya muchas de ellas en orden o que estén fuertemente anidadas.
Por otro lado, indexed get on LinkedList es mucho más lento que invocar el siguiente en iterator para LinkedList, por lo que puedes evitar ese rendimiento al tiempo que conservas la legibilidad cuando usas iteradores (explícita o implícitamente en for-each loop).
Es extraño que nadie haya mencionado lo obvio: foreach asigna memoria (en forma de un iterador), mientras que un ciclo for normal no asigna memoria. Para juegos en Android, esto es un problema, porque significa que el recolector de basura se ejecutará periódicamente. En un juego, no quieres que se ejecute el recolector de basura ... SIEMPRE. Así que no uses bucles foreach en tu método de dibujar (o renderizar).
Incluso con algo como ArrayList o Vector, donde "obtener" es una simple búsqueda de matriz, el segundo bucle aún tiene una sobrecarga adicional que la primera no tiene. Esperaría que fuera un poco más lento que el primero.
La única forma de saberlo con certeza es compararlo, e incluso eso no es tan simple como parece . El compilador JIT puede hacer cosas inesperadas en tu código.
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)
Así que estoy obligado a agregar la respuesta correcta aquí.
Directamente de la documentación del desarrollador: -
El bucle for for mejorado (también conocido 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 hacer llamadas de interfaz a hasNext () y next (). Con 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 del bucle forzado 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 de la matriz una vez por cada iteración a través del ciclo.
uno () es más rápido. Extrae 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 JIT e indistinguible de uno () para dispositivos con JIT. Utiliza la sintaxis de bucle for mejorado introducida en la versión 1.5 del lenguaje de programación Java.
Por lo tanto, debe usar el bucle for forzado de forma predeterminada, pero considere un bucle contado escrito a mano para la iteración ArrayList de rendimiento crítico.
Parece que hay 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 for-each:
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?
Lo he intentado con Java 7 y 8 en Windows 7.
Por el nombre de variable objectArrayList
, supongo que es una instancia de java.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 ya que List#get(int)
es una operación O (n).
Por lo tanto, el primer enfoque siempre es preferible a menos que el índice sea necesario por la lógica en el ciclo.
Por lo general, se debería preferir el bucle for-each. El enfoque de "obtener" puede ser más lento si la implementación de la Lista que está utilizando no admite el acceso aleatorio. Por ejemplo, si se utiliza una lista enlazada, incurrirá en un costo transversal, mientras que la aproximación para cada uno utiliza un iterador que realiza un seguimiento de su posición en la lista. Más información sobre los matices del bucle for-each .
Creo que el artículo está aquí: nueva ubicación
El enlace que se muestra aquí estaba muerto.
Sí, for-each
variante es más rápida que la normal index-based-for-loop
.
for-each
variante usa iterator
. Por lo tanto, el recorrido es más rápido que lo normal for
bucle basado en índices.
Esto se debe a que el iterator
está optimizado para atravesar, porque apunta justo antes del siguiente elemento y justo después del elemento anterior . Una de las razones por las que el index-based-for-loop
es lento es que tiene que calcular y moverse a la posición del elemento cada vez que no está con el iterator
.
Siempre es mejor usar el iterador en lugar de indexar. Esto se debe a que es muy probable que el iterador esté optimizado para la implementación de la Lista, mientras que el indexado (llamar a get) podría no serlo. Por ejemplo, LinkedList es una lista, pero la indexación a través de sus elementos será más lenta que la iteración con el iterador.
Todos estos bucles hacen exactamente lo mismo, solo quiero mostrarlos antes de arrojar mis dos centavos.
Primero, la manera 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 "¡Uy, mezcló las variables i y j en estos bucles dentro de los bucles" cosa?).
for(String s : strings) { /* do something using s */ }
En tercer lugar, el bucle for micro optimizado:
int size = strings.size();
for(int i=0;++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 en él repetida unos pocos millones de veces - esto fue usando Java 5 con jre1.6u10 en Windows en caso de que alguien esté interesado.
Si bien al menos parece ser el tercero más rápido, realmente debería preguntarse si quiere arriesgarse a implementar esta optimización de mirilla en cualquier lugar del código de bucle, ya que, por lo que he visto, el bucle no es real. Por lo general, es la parte más lenta de cualquier programa real (o tal vez estoy trabajando en el campo equivocado, quién sabe). Y también como mencioné en el pretexto del bucle de Java for-each (algunos lo llaman como iterador loop y otros como for-in loop ) es menos probable que aciertes ese error estúpido cuando lo utilizas. Y antes de debatir cómo incluso esto puede ser incluso más rápido que los otros, recuerda que javac no optimiza en absoluto el bytecode (bueno, casi de todos modos), simplemente lo compila.
Sin embargo, si te interesa la micro-optimización y / o tu software usa muchos bucles recursivos, entonces tal vez te interese el tercer tipo de bucle. Solo recuerde comparar su software bien antes y después de cambiar los bucles for a este impar y micro optimizado.
foreach aclara la intención de tu código y normalmente se prefiere a una mejora de velocidad muy pequeña, si es que hay alguna.
Cada vez que veo un ciclo 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.?
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.
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 fácil y seguro de la programación para cada uno, hay posibilidades de error propenso en la segunda forma de uso.