forループとfor-eachループの間にパフォーマンスの違いはありますか?


184

次の2つのループのパフォーマンスの違いは何ですか?

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

そして

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

@Keparo:これは「for-in」ループではなく「for each」ループです
Ande Turner

Javaでは「for each」と呼ばれますが、Objective Cになると「for In」ループと呼ばれます。
damithH 2014年


ループ性能のために拡張ここで説明されていますstackoverflow.com/questions/12155987/...は
eckes

たとえ違いがあったとしても、このコードが1秒間に数兆回実行されない限り、読みやすさを優先するのは非常にマイナーなことです。そして、とにかく時期尚早な最適化を回避するために適切なベンチマークが必要になります。
ザブザード

回答:


212

Joshua BlochによるEffective Javaの Item 46から:

リリース1.5で導入されたfor-eachループは、イテレータまたはインデックス変数を完全に非表示にすることで、煩雑さとエラーの可能性を取り除きます。結果のイディオムは、コレクションと配列に等しく適用されます。

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

コロン(:)が表示されたら、「中」と読みます。したがって、上のループは「要素内の各要素eとして」と読み取ります。配列の場合でも、for-eachループを使用してもパフォーマンスが低下しないことに注意してください。実際、配列インデックスの制限を1回しか計算しないため、状況によっては、通常のforループよりもわずかにパフォーマンスが向上する場合があります。これは手動で行うことができますが(項目45)、プログラマーは必ずしもそうするわけではありません。


48
for-eachループでは、インデックスカウンターにアクセスする方法がない(それが存在しないため)
ことに言及する価値

はい、ただし、そのカウンターはループの外側に表示されます。確かに、それは簡単な修正ですが、for-eachもそうです!
12

74
イテレータを割り当てると、パフォーマンスが低下します。Androidライブ壁紙に高度な並列コードがありました。ガベージコレクターがおかしくなっているのがわかりました。これは、for-eachループが一時的なイテレーターをさまざまな(存続期間の短い)スレッドに割り当てていたため、ガベージコレクターに多くの作業が発生したためです。通常のインデックスベースのループに切り替えると、問題が修正されました。
gsingh2011 2013

1
@ gsingh2011しかし、これはランダムアクセスリストを使用しているかどうかにも依存します。非ランダムアクセスリストへのインデックスベースのアクセスの使用は、ランダムアクセスリストでfor-eachを使用するよりもはるかに悪いと思います。インターフェースListを使用していて、実際の実装タイプがわからない場合は、リストが(実装)RandomAccessのインスタンスであるかどうかを確認できます(本当に気になる場合:docs.oracle.com/javase/8)。 /docs/api/java/util/RandomAccess.html
Puce

3
@ gsingh2011 Androidのドキュメント(developer.android.com/training/articles/perf-tips.html#Loops)では、他のコレクションではなく、ArrayListに対してのみforeachを使用するとパフォーマンスが低下することを指摘しています。それがあなたのケースだったかどうか知りたいです。
Viccari 2017

29

これらのループはすべてまったく同じです。2セント投入する前に、これらのループを表示したいだけです。

最初に、リストをループする古典的な方法:

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

第二に、エラーが発生しにくいため、推奨される方法(「ループ内でこれらのループ内の変数iとjを混合した」回数を何回行ったか)。

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

第三に、マイクロ最適化されたforループ:

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

今、実際の2セント:少なくともこれらをテストしているとき、3番目のものは、数百万回繰り返される単純な操作で各タイプのループにかかる時間をミリ秒で数えるときに最速でした-これはJava 5を使用していましたWindowsでjre1.6u10を使用すると、誰かが興味を持っている場合に備えて。

少なくとも3番目が最も高速になるようですが、ループコードのどこにでもこののぞき穴の最適化を実装するリスクを取りたいかどうかを自問する必要があります。私が見たところ、実際のループはそうではないからです。通常、実際のプログラムの中で最も時間がかかる部分です(または、私が知っている間違った分野に取り組んでいるだけかもしれません)。また、Javaのfor-eachループの口実で述べたように(一部はIteratorループと呼ばれ、他はfor-inループと呼ばれます)使用するときに特定の愚かなバグにぶつかる可能性は低くなります。そして、これが他のものよりも速くなる可能性さえあるかについて議論する前に、javacはバイトコードをまったく最適化しない(とにかくほとんどすべて)ことを覚えておいてください。コンパイルするだけです。

ただし、マイクロ最適化を行っている場合や、ソフトウェアで多くの再帰ループを使用している場合は、3番目のループタイプに興味がある可能性があります。forループを変更する前と後の両方で、この奇妙なマイクロ最適化されたものにソフトウェアのベンチマークを行うことを忘れないでください。


5
++ i <= sizeのforループは「1ベース」であることに注意してください。たとえば、ループ内のgetメソッドは、値1、2、3などに対して呼び出されます
volley

15
マイクロ最適化されたループを記述するためのより良い方法をするためのものである(INT I = 0、サイズ= strings.size(); ++ I <=サイズ;){}、それはサイズの範囲を最小化するので、これは好ましい
ドナル

1
3番目のものは、最初の要素をスキップしてループを最初に通過するときに、i = 1から開始しません。forループである必要はありません。int n = strings.length; while(n-> 0){System.out.println( "" + n + "" + strings [n]); }
Lassi Kinnunen、2016年

1
@Dónalのループパターンは最初のパターンを見逃してIOOBEを返します。これは機能します:for(int i = -1、size = list.size(); ++ i <size;)
Nathan Adams

1
「これらすべてのループはまったく同じことを行います」は正しくありません。1つはランダムアクセスを使用しget(int)、もう1つはを使用しIteratorます。n回実行しているためLinkedList、パフォーマンスfor(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }がはるかに悪い場所を検討してくださいget(int)
Steve Kuo 2016

13

for-eachループが一般的に推奨されます。使用しているList実装がランダムアクセスをサポートしていない場合、「取得」アプローチは遅くなる可能性があります。たとえば、LinkedListを使用すると、トラバーサルコストが発生しますが、for-eachアプローチでは、リスト内での位置を追跡する反復子を使用します。for-eachループのニュアンスに関する詳細情報

記事は今ここにあると思います: 新しい場所

ここに表示されているリンクは無効になっています。


12

まあ、パフォーマンスへの影響はほとんどありませんが、ゼロではありません。RandomAccessインターフェースのJavaDocを見れば:

経験則として、List実装は、クラスの一般的なインスタンスでこのループが次の場合にこのインターフェースを実装する必要があります。

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

このループよりも速く実行されます:

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

また、for-eachループはイテレータ付きのバージョンを使用しているためArrayList、たとえば、for-eachループは最速ではありません。


本当にそれぞれ?配列があるものでも?私はここで、stackoverflow.com / questions / 1006395 / …を読んでいます。イテレータは必要ありません。
OndraŽižka2015年

@OndraŽižka:for-eachループは、Iterablesをループするときに反復子を使用しますが、配列は使用しません。配列の場合、インデックス変数を持つforループが使用されます。JLSにこれに関する情報があります。
Lii

そうです、最初にtoArrayで配列に作成する必要がありますが、コストがかかります。
Lassi Kinnunen、2016年

6

残念ながら違いがあるようです。

両方の種類のループで生成されたバイトコードを見ると、ループは異なります。

これは、Log4jソースコードの例です。

/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.javaには、以下を定義するLog4jMarkerという静的内部クラスがあります。

    /*
     * 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;
    }

標準ループで:

  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

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

THAT Oracleとは何ですか?

私はWindows 7のJava 7および8でこれを試しました。


7
逆アセンブリを読み取ろうとしている人にとって、最終的な結果は、ループ内で生成されたコードは同じですが、for-eachセットアップは2番目の引数への参照を含む追加の一時変数を作成したようです。追加の隠し変数が登録されているが、パラメーター自体がコード生成中にない場合、for-eachはより高速になります。パラメータがfor(;;)の例に登録されている場合、実行時間は同じになります。お奨めのベンチマーク?
Robin Davies

4

インデックスを作成する代わりに、常にイテレータを使用することをお勧めします。これは、イテレータがList実装に対して最適化されている可能性が高いのに対し、インデックス付き(getの呼び出し)はそうではない可能性があるためです。たとえば、LinkedListはリストですが、その要素を介したインデックス作成は、イテレータを使用した反復よりも遅くなります。


10
パフォーマンスの最適化には「常に」などというものはないと思います。)
eckes

4

foreachは、コードの意図をより明確にします。これは、通常、非常に小さな速度向上よりも優先されます。

インデックス付きループを見るたびに、それを少し長く解析して、思ったとおりに機能することを確認する必要があります、それは例:んが、それは、ゼロから始めるんそれが含まれないか、などのエンドポイントを除外しますか?

私の時間のほとんどは(私が書いた、または誰かが書いた)コードの読み取りに費やされているようで、ほとんどの場合、明快さはパフォーマンスよりも重要です。Hotspotはこのような素晴らしい仕事をしているので、最近はパフォーマンスを簡単に却下できます。


4

次のコード:

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

私のシステムで次の出力が得られます。

224
-699150247503735895
221
-699150247503735895
220
-699150247503735895
219
-699150247503735895

OracleJDK 1.7 update 6でUbuntu 12.10 alphaを実行しています。

一般に、HotSpotは多くの間接参照と単純な冗長操作を最適化するため、一般に、それらが多数あるか、それらが大きくネストされていない限り、それらについて心配する必要はありません。

一方、LinkedListのインデックス付きgetは、LinkedListの反復子のnextを呼び出すよりもはるかに遅いため、反復子を使用すると(for-eachループで明示的または暗黙的に)可読性を維持しながらパフォーマンスの低下を回避できます。


3

"get"が単純な配列ルックアップであるArrayListやVectorのようなものでも、2番目のループには、最初のループにはない追加のオーバーヘッドがあります。最初よりも少し遅いと思います。


最初のループでも各要素を取得する必要があります。これを行うには、舞台裏でイテレータを作成します。それらは本当に同等です。
トカゲに請求する'11

Cの観点から考えると、イテレーターはポインターをインクリメントできますが、getは毎回iの値にポインターの幅を掛ける必要があります。
Paul Tomblin、

使用するリストの種類によって異なります。私はあなたが正しいと思いますが、getを使用することは決して速くなく、時には遅くなるでしょう。
トカゲに請求する'11


3

以下は、Android開発チームが出した違いの簡単な分析です。

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

結果があるということである差は、非常に大規模なリストと非常に控えめな環境でそれは顕著な違いである可能性があります。テストでは、for eachループに2倍の時間がかかりました。ただし、テストは400,000の整数の配列リストを超えていました。配列の要素ごとの実際の差は6 マイクロ秒でした。私はテストしていませんし、彼らも言っていませんが、プリミティブではなくオブジェクトを使用すると、差がわずかに大きくなると思いますが、それでも、要求されるものの規模がわからないライブラリコードを構築している場合を除きます繰り返しますが、違いを強調する価値はないと思います。


2

変数名でobjectArrayList、それがjava.util.ArrayList。その場合、パフォーマンスの違いは目立ちません。

一方、それがのインスタンスであるjava.util.LinkedList場合、2番目のアプローチは、List#get(int)は、O(n)操作である。

したがって、ループ内のロジックでインデックスが必要でない限り、常に最初の方法が推奨されます。


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

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

どちらも同じことを行いますが、それぞれに簡単で安全なプログラミングを使用するには、2番目の使用方法でエラーが発生しやすくなる可能性があります。


1

通常のforループはメモリを割り当てないのに対し、foreachは(イテレータの形式で)メモリを割り当てます。Androidのゲームの場合、ガベージコレクターが定期的に実行されることになるため、これは問題です。ゲームでは、ガベージコレクターを実行する必要はありません... したがって、描画(またはレンダリング)メソッドでforeachループを使用しないでください。


1

受け入れられた回答は、ArrayListの例外的なケースを除いて、質問に答えます...

ほとんどの開発者はArrayListに依存しているため(少なくとも私はそう信じています)

だから私はここに正しい答えを追加する義務があります。

開発者ドキュメントから直接:-

拡張されたforループ(「for-each」ループとも呼ばれる)は、Iterableインターフェースを実装するコレクションと配列に使用できます。コレクションの場合、hasNext()およびnext()へのインターフェース呼び出しを行うためにイテレーターが割り当てられます。ArrayListを使用すると、手書きのカウントループは(JITの有無にかかわらず)約3倍速くなりますが、他のコレクションでは、拡張されたforループ構文は明示的な反復子の使用とまったく同じになります。

配列を反復処理する方法はいくつかあります。

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()は最も遅くなります。これは、JITがループを反復するたびに1回配列の長さを取得するコストを最適化できないためです。

one()の方が高速です。ルックアップを回避し、ローカル変数にすべてを引き出します。パフォーマンスが向上するのは、アレイの長さだけです。

two()はJITのないデバイスの場合は最速であり、JITのあるデバイスの場合はone()と区別がつきません。Javaプログラミング言語のバージョン1.5で導入された拡張forループ構文を使用します。

したがって、デフォルトでは拡張forループを使用する必要がありますが、パフォーマンスが重要なArrayListの反復には手書きのカウントループを検討してください。


-2

はい、for-eachバリアントは通常よりも高速ですindex-based-for-loop

for-eachバリアントが使用しiteratorます。したがって、トラバースはforインデックスベースの通常のループよりも高速です。
これはiterator、トラバース用に最適化されているためです。これは、次の要素の直前と前の要素の直後を指しているためです。index-based-for-loop遅くなる理由の1つは、毎回要素の位置計算して移動する必要があるためiteratorです。


-3
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+" ");
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.