Javaで配列を反復処理する最速の方法:ループ変数と拡張forステートメント[複製]
-
06-07-2019 - |
質問
この質問にはすでに回答があります:
Javaでは、昔ながらの方法で配列を反復処理する方が高速ですか?
for (int i = 0; i < a.length; i++)
f(a[i]);
またはより簡潔なフォームを使用する
for (Foo foo : a)
f(foo);
ArrayListの場合、答えは同じですか?
もちろん、膨大なアプリケーションコードの場合、答えは識別できるほどの違いはないので、読みやすいように、より簡潔な形式を使用する必要があります。しかし、私が見ているコンテキストは、何十億回も実行する必要がある操作を伴うヘビーデューティな技術計算であるため、わずかな速度の違いでさえも重大になる可能性があります。
解決
配列をループする場合、それは問題ではありません-拡張forループは、とにかく配列アクセスを使用します。
たとえば、次のコードを検討してください:
public static void main(String[] args)
{
for (String x : args)
{
System.out.println(x);
}
}
javap -c Test
で逆コンパイルすると、次のようになります( main
メソッドの場合):
public static void main(java.lang.String[]);
Code:
0: aload_0
1: astore_1
2: aload_1
3: arraylength
4: istore_2
5: iconst_0
6: istore_3
7: iload_3
8: iload_2
9: if_icmpge 31
12: aload_1
13: iload_3
14: aaload
15: astore 4
17: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
20: aload 4
22: invokevirtual #3; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
25: iinc 3, 1
28: goto 7
31: return
明示的な配列アクセスを使用するように変更します:
public static void main(String[] args)
{
for (int i = 0; i < args.length; i++)
{
System.out.println(args[i]);
}
}
これは逆コンパイルします:
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: aload_0
4: arraylength
5: if_icmpge 23
8: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_0
12: iload_1
13: aaload
14: invokevirtual #3; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: iinc 1, 1
20: goto 2
23: return
拡張forループにはもう少しセットアップコードがありますが、基本的には同じことをしています。イテレーターは関係しません。さらに、JITtedがさらに類似したコードを取得することを期待しています。
提案:大幅な違いが生じると本当に考えられる場合(ループの本体が絶対に非常に小さい場合にのみ、それは ever になります)、実際のアプリケーションでベンチマークする必要があります。それが唯一の重要な状況です。
他のヒント
これは、マイクロ最適化の分野に直接該当します。本当に関係ありません。文体的には、2番目の方が好きです。なぜなら、他の何かのためにループカウンターが必要でない限り、2番目の方が簡潔だからです。そして、それはこの種のマイクロ最適化よりもはるかに重要です::読みやすさ。
とはいえ、ArrayListの場合はそれほど違いはありませんが、LinkedListの方が2番目の方がはるかに効率的です。
測定します。すべてのパフォーマンスの質問に対する答えは、VMバージョン、プロセッサ、メモリ速度、キャッシュなどに依存する可能性があります。そのため、特定のプラットフォームで測定する必要があります。
個人的には、意図がより明確であるため、2番目のバリアントを好むでしょう。パフォーマンスが問題になる場合は、とにかく後で最適化できます-そのコードがアプリケーション全体のパフォーマンスにとって本当に重要な場合。
LinkedListの場合:
for(ClassOfElement element : listOfElements) {
System.out.println(element.getValue());
}
以前に回答されました:
配列またはRandomAccessコレクションでは、次の操作を行うことで速度をわずかに向上させることができます。
List<Object> list = new ArrayList<Object>();
for (int i=0, d=list.size(); i<d; i++) {
something(list.get(i));
}
しかし、一般的には心配しません。このような最適化は、コードに0.1%以上の違いをもたらすことはありません。 -prof でjavaを呼び出して、コードが実際にどこで時間を費やしているのかを確認してください。
さらに速いのは、フォーク結合フレームワークのParallelArrayを使用することです(十分なデータセットがある場合)。