web-dev-qa-db-ja.com

ループ最適化用

List<String> flowers = new ArrayList<String>();

現在、forループは次のようになっています...

for (int i = 0; i < flowers.size(); i++) {
...
}

または、これを以下のコードのように変更する必要があります

int size = flowers.size();
for (int i = 0; i < size; i++) {
...
}

どちらがよりパフォーマンスに優れているか(私はたくさんの花を持っていると仮定して)、私はそれが後者であるべきだと推測しています。

44
Joe

for-eachループを使用することをお勧めします[読みやすい]

for (Flower flower :flowers){
    //...
}

次のコードにjavapを使用して命令をダンプしました。

public void forLoop1() {
    List<String> lst = new ArrayList<String>();
    for (int i = 0; i < lst.size(); i++) {
        System.out.println("hi");
    }
}

public void forLoop2() {
    List<String> lst = new ArrayList<String>();
    int size = lst.size();
    for (int i = 0; i < size; i++) {
        System.out.println("hi");
    }
}

public void forLoop1();
  Code:
   0:   new     #2; //class Java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method Java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   iconst_0
   9:   istore_2
   10:  iload_2
   11:  aload_1
   12:  invokeinterface #4,  1; //InterfaceMethod Java/util/List.size:()I
   17:  if_icmpge       34
   20:  getstatic       #5; //Field Java/lang/System.out:Ljava/io/PrintStream;
   23:  ldc     #6; //String hi
   25:  invokevirtual   #7; //Method Java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   28:  iinc    2, 1
   31:  goto    10
   34:  return

public void forLoop2();
  Code:
   0:   new     #2; //class Java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method Java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   invokeinterface #4,  1; //InterfaceMethod Java/util/List.size:()I
   14:  istore_2
   15:  iconst_0
   16:  istore_3
   17:  iload_3
   18:  iload_2
   19:  if_icmpge       36
   22:  getstatic       #5; //Field Java/lang/System.out:Ljava/io/PrintStream;
   25:  ldc     #6; //String hi
   27:  invokevirtual   #7; //Method Java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   30:  iinc    3, 1
   33:  goto    17
   36:  return

私には最適化されていません。

Javaバージョン "1.6.0_22" Java(TM)SEランタイム環境(ビルド1.6.0_22-b04)Java HotSpot(TM)Client VM(ビルド17.1 -b03、混合モード、共有)

したがって、前述の2つから選択する必要がある場合は、2番目に進みますが、個人的にはfor-each


for-eachパフォーマンス

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

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

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

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


こちらもご覧ください

56
Jigar Joshi

申し訳ありませんが、@ Jigarの答えは間違っています。これは正解です。 (tldr; for : eachを使用しないでください)。

import Java.util.ArrayList;
import Java.util.List;

public class LoopTest {

    public static void main(String s[]) {

        long start, end;

        List<Integer> a =  new ArrayList<Integer>();

        for (int i = 0; i < 2500000; i++) {
            a.add(i);
        }

        ///// TESTING FOR : EACH LOOP

        start = System.currentTimeMillis();

        for (Integer j : a) {
            int x = j + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ Integer j : a ] ");

        ////// TESTING DEFAULT LOOP

        start = System.currentTimeMillis();
        for (int i = 0; i < a.size(); i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < a.length; i++ ] ");


        ////// TESTING SLIGHTLY OPTIMIZED LOOP

        start = System.currentTimeMillis();
        int size = a.size();
        for (int i = 0; i < size; i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < size; i++ ] ");        

        //// TESTING MORE OPTIMIZED LOOP

        start = System.currentTimeMillis();
        for (int i = size; --i >= 0;) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = size; --i >= 0; ] ");       

    }

}

結果:

96 milli seconds for [ Integer j : a ] 
57 milli seconds for [ int i = 0; i < a.length; i++ ] 
31 milli seconds for [ int i = 0; i < size; i++ ] 
31 milli seconds for [ int i = size; --i >= 0; ] 

自分で決めても構いませんが、JVMオプティマイザーの属性が多すぎます。あなたはまだあなた自身のコードで賢くなければなりません、そして、for : each記法を使うことは良い考えではありません(ほとんどこれまで)。ご覧のとおり、サイズを独自の変数に入れることで良いアイデアが得られます。

これらの最適化の一部はJVMに依存する可能性があります(JITで開始される可能性もあります)が、何がJavaで何がJavaしません。

20
David Titarenco

size()はメソッドであるため、JVMはそれを最適化できません。また、JVMはsize()が常に同じ値を返すことを決定できない(そしてしようとしない)環境。 size()の値が変更されない場合、2番目の値はわずかにパフォーマンスが向上しますが、ゲインは非常に小さいため、使用を検討する必要はありません。

11
Raze

パフォーマンスが重要な場合は、単純なカウンターループを使用しますが、98%の場合、コードの明快さと単純さがはるかに重要で(1000x以上など)、for-eachループを使用する必要があります。

@Davidは、カウンターを使用する方が速いことを指摘していますが、10,000エントリであっても、その差はサブマイクロ秒であることを指摘します。

10,000を超えるエントリのコレクションがある場合は、あらゆる可能性を総当たりで繰り返してはいけません。 Mapのようなルックアップを備えたコレクションは、考えているものすべてに適したソリューションである可能性が高くなります。エントリ数が10,000をはるかに下回る場合、パフォーマンスはそれほど重要ではありません。

9
Peter Lawrey

反復中に配列リストが変更された場合の動作は異なります。しかし、あなたはそうしないと思います。私のテストによると、後者は通常より高速です(特にAndroidのようなシステムで)。次のように書きます。

for (int i = 0, size = flowers.size(); i < size; i++) {
    ...
}
6
Thomas Mueller

Java言語仕様(14.14.1)から:

基本的なforステートメントは、初期化コードその後、式を実行、ステートメント、および更新コード繰り返しを、式の値がfalseになるまで実行します。

式は最初の例ではi < flowers.size()であり、反復ごとに1回評価されます。特別なケースでは、ArrayList上のflowers.getSize()は非常に短いメソッドであるため、顕著な違いはありません。ただし、一般に、式の結果が各反復で同じであり、費用がかかる場合は、事前計算を行います。

結果:これは、Java仮想マシンのすべての実装で同じ出力を生成し、それを証明する必要がありますExpressionは、各反復で1回評価されます。

int counter2 = 10;
for (int counter1 = 0; counter1 < counter2; counter1++) {
  System.out.println(counter1 + ", " + counter2);
  counter2--;
}

出力:

0, 10
1, 9
2, 8
3, 7
4, 6
4
Andreas_D

最適なオプションは

[ int i = 0; i < size; i++ ]

結果は、どのJVMや-client vs -serverのような他の設定に基づいて異なります。これは、測定の一部が非常に小さいため、ナノ秒を使用して測定する必要があり、多くのテストを行う必要があります。結果。また、これらの種類のテストには、ループをゼロに最適化するJVMの習慣があります。コードの最後に変更する変数を画面に配置することで、このリスクを排除しようとしました。

1.6
-server
7.968242071 milli seconds for [ Integer j : a ] 
7.206275775999999 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.5864E-5 milli seconds for [ int i = 0; i < size; i++ ] 
14.774186076999998 milli seconds for [ int i = size; --i >= 0; ] 

-client
83.36101683999999 milli seconds for [ Integer j : a ] 
44.288568631 milli seconds for [ int i = 0; i < a.length; i++ ]  
2.3191E-5 milli seconds for [ int i = 0; i < size; i++ ] 
24.826621246 milli seconds for [ int i = size; --i >= 0; ] 

1.7

-server
7.029150422 milli seconds for [ Integer j : a ] 
6.6269827779999995 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.3852E-5 milli seconds for [ int i = 0; i < size; i++ ] 
13.842110377 milli seconds for [ int i = size; --i >= 0; ] 
13.868426141 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
1.6618000000000003E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 

-client
7.382479727 milli seconds for [ Integer j : a ] 
6.748068759 milli seconds for [ int i = 0; i < a.length; i++ ]  
1.4162999999999998E-5 milli seconds for [ int i = 0; i < size; i++ ] 
13.951547335999999 milli seconds for [ int i = size; --i >= 0; ] 
13.929234053999998 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
1.6873E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 

テストコード:

public static void main(String s[]) {
long start=0, end = 0, delta = 0;
//int[] a = new int[2500000];
List<Integer> a = new ArrayList<Integer>();
int x = 0;

for (int i = 0; i < 2500000; i++) {
    a.add(i);
}

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (Integer j : a) {
         x = j + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ Integer j : a ] ");


start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = 0; i < a.size(); i++) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.length; i++ ]  ");

int size = a.size();

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.currentTimeMillis();

    for (int i = 0; i < size; i++) {
         x = a.get(i) + 3;
    }
    end = System.currentTimeMillis();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < size; i++ ] ");

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = size; --i >= 0;) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = size; --i >= 0; ] ");


start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.nanoTime();
    for (int i = a.size()-1; i >= 0; i--) {
         x = a.get(i) + 3;
    }
    end = System.nanoTime();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = a.size()-1; i >= 0; i-- ] ");

start=0; end = 0; delta = 0;
for (int ctr = 0; ctr < 1000; ctr++) {
    start = System.currentTimeMillis();

    for (int i = 0; i < a.size(); i++) {
         x = a.get(i) + 3;
    }
    end = System.currentTimeMillis();
    delta += end - start;
}
System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.size(); i++ ] ");        

System.out.println(x);
}
3
medv4380

これは、これがいかに状況的であるかの例による明確化です。

ループfor (int i = 0; i < list.size(); i++)の「通常」と、ループfor (int i = -1, size = list.size(); ++i < size;)に最適化されたマイクロの実行をテストしました。コマンドラインからEclipseの両方でテストを実行したところ、大きな違いに気付きました。

Eclipseで実行した結果:

Time for Original: 32552 ms   Time for MicroOptimized 32707 ms
Fastest Loop: Original
Slowest loop takes 0.47616121897272057% more time

コマンドラインから実行した結果:

Time for Original: 274489 ms   Time for MicroOptimized 30516 ms
Fastest Loop: MicroOptimized
Slowest loop takes 799.4920697339101% more time

そのため、Eclipseでは、2つのforループに同じ時間がかかりますが、コマンドラインから実行した場合、元のバージョンはマイクロ最適化バージョンよりも800%時間がかかります。違いの大きさは私の心を吹き飛ばします。 Eclipseは、いくつかのスマートな最適化トリックを適用する別のJVMを使用していると思います。

ただし、これは、マイクロ最適化バージョンの使用を開始する必要があるという意味ではありません。ほとんどすべての場合、反復するリストは非常に小さいため、パフォーマンスの違いは無視できます。そして、誰もがすぐに認識して理解できる標準バージョンを使用することで得られる読みやすさは、目立たないパフォーマンスの向上よりも有益です。

完全を期すために、これは私が実行したコードです。

public static void main(String[] args) {
        List<Byte> list = initializeList();
        byte value = 0;
        final int NUM_LOOPS = 100;

        long startOriginal, startOptimized, endOriginal, endOptimized;

        startOptimized = System.currentTimeMillis();
        for (int j = 0; j < NUM_LOOPS; j++) {
            for (int i = -1, size = list.size(); ++i < size;) {
                value = list.get(i);
            }
        }
        endOptimized = System.currentTimeMillis();

        startOriginal = System.currentTimeMillis();
        for (int j = 0; j < NUM_LOOPS; j++) {
            for (int i = 0; i < list.size(); i++) {
                value = list.get(i);
            }
        }
        endOriginal = System.currentTimeMillis();

        System.out.println(value);
        printResults(startOriginal, endOriginal, startOptimized, endOptimized);
    }

    private static void printResults(long startOriginal, long endOriginal,
            long startOptimized, long endOptimized) {

        long timeOriginal = endOriginal - startOriginal;
        long timeOptimized = endOptimized - startOptimized;

        long diff = Math.abs(timeOriginal - timeOptimized);
        long min = Math.min(timeOriginal, timeOptimized);

        System.out.println("Time for Original: " + timeOriginal + " ms"
                + "   Time for MicroOptimized " + timeOptimized + " ms");

        System.out.println("Fastest Loop: "
                + ((timeOriginal < timeOptimized) ? "Original"
                        : "MicroOptimized"));

        System.out.println("Slowest loop takes " + ((double) 100 * diff / min)
                + "% more time");       
    }

    public static List<Byte> initializeList(){
        List<Byte> list = new ArrayList<Byte>();
        final Byte ONE = new Byte((byte) 1);

        for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
            list.add(ONE);
        }

        return list;
    }
}
2
Alderath

また、メソッド呼び出しをソースコレクションとして使用すると、パフォーマンスに影響があるかどうか疑問に思う場合もあります。つまり、メソッドは何度も呼び出されますが、答えはノーです。以下に例を示します。

import Java.util.*;
public class TestForeach {
    public static void main (String[] args) {

        for (String s : getStrings()) {
            System.out.println("The string was: "+s);
        }
    } 

    private static List<String> getStrings() {
        System.out.println("IN GET STRINGS");
        return Arrays.asList("A","B","C");
    }
}

これにより、次の結果が得られます。

IN GET STRINGS
The string was: A
The string was: B
The string was: C

したがって、メソッドは1回だけ呼び出されます。

1
ACV

シンプルで効果的

 for (ConfigDataModel.VisaTypesBean.AddedBean visatype : visaTypesBeans) {
                            if (visatype.getId() == 24) {

                            }
0

どちらでもかまいません。 JVMに応じて、2番目のクロックサイクルは数クロックサイクル速くなる場合がありますが、計り知れないまたは取るに足らない違いになります。これらのタイプの準最適化に注意してください。すべてのCPUティックがカウントされるリアルタイムシステムを構築している場合を除き、複雑さとエラーの原因が増えるだけです。

イテレータ構造を使用することをお勧めします(既に提案されているように)

For (Flower flower: flowers) { ...

明確で、柔軟性があり、予測可能です。

0
pap

このすべての番号付け、イテレータ、およびコード作成時のチェックを回避するには、パフォーマンスが最大になる、次の最も読みやすいコードを使用します。これが最大のパフォーマンスを発揮する理由(詳細が近づいています)

for (Object object : aCollection) { 
// Do something here
}

インデックスが必要な場合:上記の2つの形式から選択するには:チェックにはローカル変数を使用しているため、2番目の形式の方が適しています。メソッドが終了すると、変数はスタックのゴミ箱に移動します。

0
GingerHead

私のアプローチはこの問題に関して少し異なります。私にとって、どの方法を選択するかは問題ではありません。理由は、最適なoptimizedメソッドで得られる「パフォーマンスの向上」であり、2,500,000回の反復で最大50msです!! (@Davidの投稿による)。そして明らかに、この改善はoptimizedソリューションを見つけるために貴重な時間を無駄にしたいようなものではありません。

(それでも、OPの元の質問に従って、最後のアプローチを提案したいと思います。)

答えは少し奇妙で一般的ではありませんが、これは現実です。

0
Nirmit Shah