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

StackOverflow https://stackoverflow.com/questions/256859

  •  05-07-2019
  •  | 
  •  

質問

もしあれば、次の2つのループのパフォーマンスの違いは何ですか?

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

and

for (int i=0; i<objectArrayList.size(); i++) {
    objectArrayList.get(i).DoSomething();
}
役に立ちましたか?

解決

効果的なJava 、Joshua Bloch氏:

  

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

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

コロン(:)が表示されたら、次のように読みます   &#8220; in。&#8221;したがって、上記のループは次のようになります。   &#8220;要素内の各要素eについて&#8221;注意   パフォーマンスの低下がないこと   for-eachループを使用するため、   配列。実際には、わずかな   通常よりもパフォーマンスが優れている   状況によってはforループ   配列インデックスの制限を計算します   1回だけ。あなたはこれを行うことができますが   ハンド(アイテム45)、プログラマーはドン&#8217; t   常にそうします。

他のヒント

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

まず、リストをループする古典的な方法:

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

2つ目は、エラーが発生しにくいため、好ましい方法です(「ループ内でこれらのループ内で変数iとjを混合した」ということを何回繰り返しましたか?)。

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

第三に、最適化されたforループ:

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

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

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

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

通常、for-eachループが優先されます。 &quot; get&quot;使用しているList実装がランダムアクセスをサポートしていない場合、アプローチは遅くなる可能性があります。たとえば、LinkedListを使用する場合、トラバーサルコストが発生しますが、for-eachアプローチでは、リスト内の位置を追跡するイテレーターを使用します。 for-eachループのニュアンス

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

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

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

  

経験則として、リスト   実装はこれを実装する必要があります   インターフェースの場合、の典型的なインスタンス   クラス、このループ:

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ループは最速ではありません。

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

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

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でこれを試しました。

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

foreachはコードの意図を明確にしますが、通常、非常にわずかな速度の改善よりも優先されます(ある場合)。

インデックス付きループが表示されるたびに、それが私が考えていることを確認するために、もう少し解析する必要があります。ゼロから始まりますか、エンドポイントを含めますか、除外しますか?

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

次のコード:

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のインデックス付き取得は、LinkedListのイテレータでnextを呼び出すよりもはるかに遅いため、イテレータを使用する場合(for-eachループで明示的または暗黙的に)、読みやすさを維持しながらパフォーマンスヒットを回避できます。

ArrayListやVectorのようなものでも、「get」はは単純な配列ルックアップであり、2番目のループにはまだ最初のループにはない追加のオーバーヘッドがあります。私はそれが最初よりも少し遅いと予想しています。

確実に知る唯一の方法は、ベンチマークすることです。それでもそれほど単純ではありません聞こえるかもしれません。 JITコンパイラーは、コードに対して非常に予期しないことを実行できます。

これは、Android開発チームによって出された違いの簡単な分析です。

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

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

変数名 objectArrayList により、それが java.util.ArrayList のインスタンスであると想定します。その場合、パフォーマンスの違いは目立ちません。

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

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

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

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

どちらも同じことを行いますが、for-eachで簡単かつ安全にプログラミングを使用するために、2番目の使用方法でエラーが発生する可能性があります。

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

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

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

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

開発者ドキュメントからのストレート:-

拡張forループ(「for-each」ループとも呼ばれる)は、Iterableインターフェイスを実装するコレクションや配列に使用できます。コレクションでは、hasNext()およびnext()へのインターフェース呼び出しを行うためにイテレーターが割り当てられます。 ArrayListを使用すると、手書きのカウントループは約3倍高速になります(JITの有無にかかわらず)が、他のコレクションの場合、拡張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の反復処理には手書きのカウントループを検討してください。

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

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

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top