it-swarm.com.de

Gibt es einen Leistungsunterschied zwischen einer for-Schleife und einer for-each-Schleife?

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();
}
170
eflles

Aus Punkt 46 in Effektives Java von Joshua Bloch:

Die in Release 1.5 eingeführte For-Each-Schleife beseitigt das Durcheinander 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 elements". Beachten Sie, dass es keine Leistungseinbußen für die Verwendung der for-each-Schleife gibt, auch für Arrays . Tatsächlich kann es unter bestimmten Umständen einen leichten Leistungsvorteil gegenüber einer gewöhnlichen for-Schleife bieten, da es das Limit des Array-Index nur einmal berechnet. Sie können dies zwar manuell tun (Punkt 45), Programmierer tun dies jedoch nicht immer.

196
Vijay Dev

Alle diese Loops machen genau dasselbe, ich möchte sie nur zeigen, bevor ich meine zwei Cent hineinwerfe.

Erstens die klassische Art, List 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 das "oops, mixed the variables i und j in diesen Schleifen in Schleifen" -Ding durchgeführt?).

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

Drittens ist das Mikro für Schleife optimiert:

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

Jetzt die eigentlichen zwei Cent: Zumindest als ich diese getestet habe, war der dritte der schnellste, als ich Millisekunden gezählt habe, wie lange es für jede Art von Schleife gedauert hat, mit einer einfachen Operation, die einige Millionen Mal wiederholt wurde - dies verwendete Java 5 mit jre1.6u10 unter Windows, falls jemand interessiert ist.

Zumindest scheint es so zu sein, dass die dritte die schnellste ist, aber Sie sollten sich wirklich fragen, ob Sie das Risiko eingehen möchten, diese Gucklochoptimierung überall in Ihrem Loop-Code zu implementieren, da nach dem, was ich gesehen habe, das eigentliche Looping nicht funktioniert. Normalerweise ist es 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 loop erwähnt habe (einige bezeichnen sie als Iterator loop und andere als for- In der Schleife) ist es weniger wahrscheinlich, dass Sie diesen einen bestimmten dummen Fehler finden, wenn Sie ihn verwenden. Und bevor Sie darüber debattieren, wie dies sogar noch schneller sein kann als die anderen, denken Sie daran, dass Javac den Bytecode überhaupt nicht optimiert (naja, fast sowieso), 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 prüfen.

27
P Arrayah

Die For-Each-Schleife sollte im Allgemeinen bevorzugt werden. Der "get" -Ansatz ist möglicherweise langsamer, wenn die von Ihnen verwendete List-Implementierung keinen wahlfreien Zugriff unterstützt. Wenn beispielsweise eine LinkedList verwendet wird, entstehen Ihnen Traversalkosten, wohingegen der For-Each-Ansatz einen Iterator verwendet, der die Position in der Liste verfolgt. Weitere Informationen zu Nuancen der for-each-Schleife .

Ich denke der Artikel ist jetzt hier: neuer Ort

Der hier gezeigte Link war tot.

13
Zach Scrivena

Nun, die Auswirkungen auf die Leistung sind größtenteils unbedeutend, aber nicht gleich Null. Wenn Sie sich JavaDoc von RandomAccess interface ansehen:

Als Faustregel sollte eine List-Implementierung diese Schnittstelle implementieren, wenn für typische Instanzen der Klasse diese Schleife:

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 Version mit Iterator verwendet, sodass für ArrayList beispielsweise für jede Schleife nicht die schnellste ist.

11
Sarmun

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 los mit Oracle?

Ich habe dies mit Java 7 und 8 unter Windows 7 versucht.

5
Gary Gregory

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 , z. Beginnt es bei Null, schließt es den Endpunkt usw. ein oder aus?

Die meiste Zeit verbringe ich damit, Code zu lesen (den ich oder jemand anders geschrieben habe), und Klarheit ist fast immer wichtiger als Leistung. Heutzutage ist es einfach, die Leistung zu reduzieren, weil Hotspot einen so tollen Job macht.

4
Fortyrunner

Es ist immer besser, den Iterator anstelle der Indizierung zu verwenden. Dies liegt daran, dass der Iterator höchstwahrscheinlich für die List-Implementierung optimiert ist, während die Indizierung (der Aufruf von get) möglicherweise nicht erfolgt. Beispielsweise ist LinkedList eine Liste, aber die Indizierung durch ihre Elemente ist langsamer als die Iteration mit dem Iterator.

4
Steve Kuo

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 aus:

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 Reduntant-Vorgänge, sodass Sie sich im Allgemeinen keine Sorgen machen sollten, es sei denn, es sind viele aufeinanderfolgende oder stark verschachtelte Vorgänge vorhanden.

Auf der anderen Seite ist das indizierte Abrufen von LinkedList viel langsamer als das Aufrufen von next on iterator für LinkedList, sodass Sie diesen Leistungseinbruch vermeiden können, während die Lesbarkeit erhalten bleibt, wenn Sie Iteratoren verwenden (explizit oder implizit in jeder Schleife).

4
Wibowit

Sogar mit etwas wie einer ArrayList oder einem Vector, 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.

3
Paul Tomblin

Die einzige Möglichkeit, dies sicher zu wissen, ist das Benchmarking, und selbst das ist nicht so einfach, wie es sich anhört . Der JIT-Compiler kann sehr unerwartete Dinge mit Ihrem Code tun.

3

Hier ist eine kurze Analyse des Unterschieds, der vom Android Entwicklerteam festgestellt wurde:

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

Das Ergebnis ist, dass es is einen Unterschied gibt, und in sehr zurückhaltenden Umgebungen mit sehr großen Listen könnte es einen spürbaren Unterschied geben. Bei ihren Tests dauerte die für jede Schleife doppelt so lange. Ihre Tests lagen jedoch über einer Arrayliste von 400.000 ganzen Zahlen. Die tatsächliche Differenz pro Element im Array betrug 6 Mikrosekunden. Ich habe es nicht getestet und sie sagten es nicht, aber ich würde erwarten, dass der Unterschied etwas größer ist, wenn Sie Objekte anstelle von Primitiven verwenden, aber selbst dann, wenn Sie keinen Bibliothekscode erstellen, bei dem Sie keine Ahnung haben, in welchem ​​Maßstab Sie gefragt werden Um es noch einmal zu wiederholen, denke ich, dass der Unterschied es nicht wert ist, betont zu werden.

3
TBridges42

Bei dem Variablennamen objectArrayList gehe ich davon aus, dass es sich um eine Instanz von Java.util.ArrayList Handelt. In diesem Fall wäre der Leistungsunterschied nicht zu bemerken.

Wenn es sich hingegen um eine Instanz von Java.util.LinkedList Handelt, ist der zweite Ansatz viel langsamer, da die List#get(int) eine O(n) -Operation ist.

Daher wird der erste Ansatz immer bevorzugt, es sei denn, der Index wird von der Logik in der Schleife benötigt.

2
Changgeng

Es ist seltsam, dass niemand das Offensichtliche erwähnt hat - foreach weist Speicher zu (in Form eines Iterators), wohingegen eine normale for-Schleife keinen Speicher zuweist. Bei Spielen unter Android ist dies ein Problem, da der Garbage Collector in regelmäßigen Abständen ausgeführt wird. In einem Spiel willst du nicht, dass der Müllmann läuft ... JEDERZEIT. Verwenden Sie also keine foreach-Schleifen in Ihrer Zeichen- (oder Rendermethode).

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

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

Beide tun dasselbe, aber für die einfache und sichere Programmierung gibt es Möglichkeiten für fehleranfällige zweite Verwendungsart.

1
Santosh

Akzeptierte Antwort beantwortet die Frage, abgesehen vom Ausnahmefall von ArrayList ...

Da sich die meisten Entwickler auf ArrayList verlassen (zumindest glaube ich das)

Deshalb 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, die die Iterable-Schnittstelle implementieren, und für Arrays verwendet werden. Bei Sammlungen wird ein Iterator zugewiesen, um Schnittstellenaufrufe für 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 Auflistungen entspricht die Syntax der erweiterten Schleife 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.

eins () ist schneller. Es zieht alles in lokale Variablen und vermeidet das Nachschlagen. 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 in Version 1.5 der Programmiersprache Java) eingeführte erweiterte for-Loop-Syntax.

Daher sollten Sie standardmäßig die erweiterte for-Schleife verwenden, für die leistungskritische ArrayList-Iteration jedoch eine handgeschriebene gezählte Schleife in Betracht ziehen.