Was ist, wenn überhaupt, der Leistungsunterschied zwischen den folgenden beiden Schleifen?
for (Object o: objectArrayList) {
o.DoSomething();
}
und
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
Was ist, wenn überhaupt, der Leistungsunterschied zwischen den folgenden beiden Schleifen?
for (Object o: objectArrayList) {
o.DoSomething();
}
und
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
Antworten:
Aus Punkt 46 in Effective Java von Joshua Bloch:
Die in Version 1.5 eingeführte for-each-Schleife beseitigt die Unordnung und die Möglichkeit von Fehlern, indem der Iterator oder die Indexvariable vollständig ausgeblendet werden. Die resultierende Redewendung gilt gleichermaßen für Sammlungen und Arrays:
// The preferred idiom for iterating over collections and arrays for (Element e : elements) { doSomething(e); }
Wenn Sie den Doppelpunkt (:) sehen, lesen Sie ihn als "in". Daher lautet die obige Schleife "für jedes Element e in Elementen". Beachten Sie, dass die Verwendung der for-each-Schleife auch für Arrays keine Leistungseinbußen nach sich zieht. Tatsächlich kann es unter bestimmten Umständen einen leichten Leistungsvorteil gegenüber einer normalen for-Schleife bieten, da es die Grenze des Array-Index nur einmal berechnet. Während Sie dies von Hand tun können (Punkt 45), tun dies Programmierer nicht immer.
Alle diese Schleifen machen genau das Gleiche. Ich möchte sie nur zeigen, bevor ich meine zwei Cent einsetze.
Erstens die klassische Art, eine Liste zu durchlaufen:
for (int i=0; i < strings.size(); i++) { /* do something using strings.get(i) */ }
Zweitens der bevorzugte Weg, da er weniger fehleranfällig ist (wie oft haben SIE die Sache "oops, gemischt die Variablen i und j in diesen Schleifen innerhalb von Schleifen" gemacht?).
for (String s : strings) { /* do something using s */ }
Drittens die mikrooptimierte Schleife:
int size = strings.size();
for (int i = -1; ++i < size;) { /* do something using strings.get(i) */ }
Jetzt die tatsächlichen zwei Cent: Zumindest als ich diese testete, war der dritte der schnellste, wenn Millisekunden gezählt wurden, wie lange es für jeden Schleifentyp gedauert hat, wobei eine einfache Operation einige Millionen Mal wiederholt wurde - dies war Java 5 mit jre1.6u10 unter Windows, falls jemand interessiert ist.
Während es zumindest so zu sein scheint, dass der dritte der schnellste ist, sollten Sie sich wirklich fragen, ob Sie das Risiko eingehen möchten, diese Gucklochoptimierung überall in Ihrem Schleifencode zu implementieren, da nach meinem Dafürhalten keine tatsächliche Schleife vorliegt. Normalerweise der zeitaufwändigste Teil eines echten Programms (oder ich arbeite nur auf dem falschen Gebiet, wer weiß). Und auch, wie ich im Vorwand für die Java- for-each-Schleife erwähnt habe (einige bezeichnen sie als Iterator-Schleife und andere als for-in-Schleife ), ist es weniger wahrscheinlich, dass Sie diesen einen dummen Fehler treffen, wenn Sie sie verwenden. Und bevor Sie darüber diskutieren, wie dies sogar schneller sein kann als die anderen, denken Sie daran, dass javac den Bytecode überhaupt nicht optimiert (na ja, fast überhaupt nicht), sondern nur kompiliert.
Wenn Sie sich jedoch für Mikrooptimierung interessieren und / oder Ihre Software viele rekursive Schleifen verwendet, sind Sie möglicherweise am dritten Schleifentyp interessiert. Denken Sie daran, Ihre Software sowohl vor als auch nach dem Ändern der for-Schleifen auf diese seltsame, mikrooptimierte zu überprüfen.
get(int)
, der andere verwendet einen Iterator
. Überlegen Sie, LinkedList
wo die Leistung von for(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
weitaus schlechter ist, da sie get(int)
n-mal ausgeführt wird.
Die for-each-Schleife sollte im Allgemeinen bevorzugt werden. Der "get" -Ansatz ist möglicherweise langsamer, wenn die von Ihnen verwendete List-Implementierung keinen Direktzugriff unterstützt. Wenn beispielsweise eine LinkedList verwendet wird, entstehen Ihnen Durchquerungskosten, während für jeden Ansatz ein Iterator verwendet wird, der seine Position in der Liste verfolgt. Weitere Informationen zu den Nuancen der for-each-Schleife .
Ich denke, der Artikel ist jetzt hier: neuer Ort
Der hier gezeigte Link war tot.
Nun, die Auswirkungen auf die Leistung sind größtenteils unbedeutend, aber nicht Null. Wenn Sie sich JavaDoc der RandomAccess
Schnittstelle ansehen :
Als Faustregel sollte eine List-Implementierung diese Schnittstelle implementieren, wenn für typische Instanzen der Klasse diese Schleife gilt:
for (int i=0, n=list.size(); i < n; i++) list.get(i);
läuft schneller als diese Schleife:
for (Iterator i=list.iterator(); i.hasNext();) i.next();
Und für jede Schleife wird eine Version mit Iterator verwendet, sodass ArrayList
beispielsweise für jede Schleife nicht die schnellste ist.
Es scheint leider einen Unterschied zu geben.
Wenn Sie sich den generierten Bytecode für beide Arten von Schleifen ansehen, sind sie unterschiedlich.
Hier ist ein Beispiel aus dem Log4j-Quellcode.
In /log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java haben wir eine statische innere Klasse namens Log4jMarker, die definiert:
/*
* 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;
}
Mit Standardschleife:
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
Mit für jeden:
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
Was ist mit DIESEM Oracle los?
Ich habe dies mit Java 7 und 8 unter Windows 7 versucht.
Es ist immer besser, den Iterator zu verwenden, als zu indizieren. Dies liegt daran, dass der Iterator höchstwahrscheinlich für die List-Implementierung optimiert ist, während indiziert (Aufruf von get) möglicherweise nicht. Zum Beispiel ist LinkedList eine Liste, aber die Indizierung durch ihre Elemente ist langsamer als die Iteration mit dem Iterator.
foreach macht die Absicht Ihres Codes klarer und dies wird normalerweise einer geringfügigen Geschwindigkeitsverbesserung vorgezogen - falls vorhanden.
Immer wenn ich eine indizierte Schleife sehe, muss ich sie etwas länger analysieren, um sicherzustellen, dass sie das tut, was ich denke. ZB Beginnt sie bei Null, schließt sie den Endpunkt ein oder aus usw.?
Die meiste Zeit verbringe ich damit, Code zu lesen (den ich geschrieben habe oder den jemand anderes geschrieben hat), und Klarheit ist fast immer wichtiger als Leistung. Es ist heutzutage einfach, die Leistung zu verwerfen, weil Hotspot so einen tollen Job macht.
Der folgende Code:
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);
}
}
Gibt folgende Ausgabe auf meinem System:
224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895
Ich verwende Ubuntu 12.10 alpha mit OracleJDK 1.7 Update 6.
Im Allgemeinen optimiert HotSpot viele Indirektionen und einfache redundante Vorgänge. Im Allgemeinen sollten Sie sich also keine Sorgen machen, es sei denn, es gibt viele davon in Folge oder sie sind stark verschachtelt.
Auf der anderen Seite ist das indizierte Abrufen von LinkedList viel langsamer als das Aufrufen des nächsten Iterators für LinkedList, sodass Sie diesen Leistungseinbruch vermeiden und gleichzeitig die Lesbarkeit beibehalten können, wenn Sie Iteratoren verwenden (explizit oder implizit in jeder Schleife).
Selbst bei so etwas wie einer ArrayList oder einem Vektor, bei dem "get" eine einfache Array-Suche ist, hat die zweite Schleife noch zusätzlichen Overhead, den die erste nicht hat. Ich würde erwarten, dass es ein bisschen langsamer ist als das erste.
Die einzige Möglichkeit, dies sicher zu wissen, besteht darin, es zu bewerten, und selbst das ist nicht so einfach, wie es sich anhört . Der JIT-Compiler kann sehr unerwartete Dinge mit Ihrem Code tun.
Hier ist eine kurze Analyse des Unterschieds, den das Android-Entwicklungsteam herausgestellt hat:
https://www.youtube.com/watch?v=MZOf3pOAM6A
Das Ergebnis ist , dass es ist ein Unterschied, und in sehr zurückhaltenden Umgebungen mit sehr großen Listen könnte es ein spürbarer Unterschied. Bei ihren Tests dauerte die für jede Schleife doppelt so lange. Ihre Tests wurden jedoch über eine Arrayliste von 400.000 ganzen Zahlen durchgeführt. Die tatsächliche Differenz pro Element im Array betrug 6 Mikrosekunden . Ich habe nicht getestet und sie haben es nicht gesagt, aber ich würde erwarten, dass der Unterschied bei der Verwendung von Objekten anstelle von Grundelementen etwas größer ist, aber auch dann, wenn Sie keinen Bibliothekscode erstellen, bei dem Sie keine Ahnung haben, in welchem Umfang Sie gefragt werden Ich denke, der Unterschied ist es nicht wert, darüber nachzudenken.
Beim Variablennamen objectArrayList
nehme ich an, dass dies eine Instanz von ist java.util.ArrayList
. In diesem Fall wäre der Leistungsunterschied nicht wahrnehmbar.
Wenn es sich dagegen um eine Instanz von handelt java.util.LinkedList
, ist der zweite Ansatz viel langsamer, da List#get(int)
es sich um eine O (n) -Operation handelt.
Daher wird der erste Ansatz immer bevorzugt, es sei denn, der Index wird von der Logik in der Schleife benötigt.
Es ist seltsam, dass niemand das Offensichtliche erwähnt hat - foreach weist Speicher zu (in Form eines Iterators), während eine normale for-Schleife keinen Speicher zuweist. Bei Spielen unter Android ist dies ein Problem, da der Garbage Collector regelmäßig ausgeführt wird. In einem Spiel soll der Müllsammler nicht laufen ... NIEMALS. Verwenden Sie also keine foreach-Schleifen in Ihrer Zeichen- (oder Render-) Methode.
Akzeptierte Antwort beantwortet die Frage, abgesehen vom Ausnahmefall von ArrayList ...
Da sich die meisten Entwickler auf ArrayList verlassen (zumindest glaube ich das)
Daher bin ich verpflichtet, hier die richtige Antwort hinzuzufügen.
Direkt aus der Entwicklerdokumentation: -
Die erweiterte for-Schleife (manchmal auch als "for-each" -Schleife bezeichnet) kann für Sammlungen verwendet werden, die die Iterable-Schnittstelle implementieren, sowie für Arrays. Bei Sammlungen wird ein Iterator zugewiesen, um Schnittstellenaufrufe an hasNext () und next () durchzuführen. Mit einer ArrayList ist eine handgeschriebene gezählte Schleife etwa dreimal schneller (mit oder ohne JIT), aber für andere Sammlungen entspricht die erweiterte for-Schleifensyntax genau der expliziten Verwendung von Iteratoren.
Es gibt verschiedene Alternativen zum Durchlaufen eines Arrays:
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 () ist am langsamsten, da die JIT die Kosten für das einmalige Abrufen der Array-Länge für jede Iteration durch die Schleife noch nicht optimieren kann.
one () ist schneller. Es zieht alles in lokale Variablen und vermeidet die Suche. Nur die Array-Länge bietet einen Leistungsvorteil.
two () ist für Geräte ohne JIT am schnellsten und für Geräte mit JIT nicht von one () zu unterscheiden. Es verwendet die erweiterte for-Schleifensyntax, die in Version 1.5 der Java-Programmiersprache eingeführt wurde.
Daher sollten Sie standardmäßig die erweiterte for-Schleife verwenden, jedoch eine handgeschriebene gezählte Schleife für die leistungskritische ArrayList-Iteration in Betracht ziehen.
Ja, die for-each
Variante ist schneller als normal index-based-for-loop
.
for-each
Variante verwendet iterator
. Das Durchlaufen ist also schneller als eine normale for
Schleife, die indexbasiert ist.
Dies liegt daran iterator
, dass sie für das Durchlaufen optimiert sind, da sie kurz vor dem nächsten Element und unmittelbar nach dem vorherigen Element zeigen . Einer der Gründe dafür, index-based-for-loop
langsam zu sein, ist, dass es jedes Mal , wenn es nicht mit dem ist, berechnen und sich zur Elementposition bewegen mussiterator
.