¿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();
}
¿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();
}
Respuestas:
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.
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.
get(int)
, el otro usa un Iterator
. Considere LinkedList
dó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.
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.
Bueno, el impacto en el rendimiento es en su mayoría insignificante, pero no es cero. Si nos fijamos en JavaDoc de la RandomAccess
interfaz:
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, ArrayList
por ejemplo, for-each loop no es más rápido.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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).
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.
Sí, la for-each
variante es más rápida de lo normal.index-based-for-loop
.
for-each
usos variantes iterator
. Por lo tanto, el desplazamiento es más rápido que el for
bucle normal, que se basa en índices.
Esto se debe a que iterator
está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-loop
ser lento es que tiene que calcular y moverse a la posición del elemento cada vez que no está con el iterator
.