web-dev-qa-db-ja.com

Javaで配列を反復処理する最速の方法:ループ変数と拡張forステートメント

Javaでは、昔ながらの方法で配列を反復処理するのが高速ですか、

for (int i = 0; i < a.length; i++)
    f(a[i]);

または、より簡潔な形式を使用して、

for (Foo foo : a)
    f(foo);

ArrayListの場合、答えは同じですか?

もちろん、膨大な量のアプリケーションコードについては、識別可能な違いはないので、答えは読みやすいように、より簡潔な形式を使用する必要があります。しかし、私が検討しているコンテキストは、何十億回も実行する必要がある操作を伴うヘビーデューティな技術計算です。

28
rwallace

配列をループしている場合、それは重要ではありません-拡張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を行う)、実際のアプリケーションでベンチマークを行う必要があります。それが重要な唯一の状況です。

55
Jon Skeet

これは micro-optimization の分野に正真正銘で当てはまります。本当に関係ありません。文体的には、他の何かのためにループカウンターが必要でない限り、2番目の方が簡潔であるため、常に2番目の方が好きです。そして、それはこの種のマイクロ最適化よりもはるかに重要:可読性です。

そうは言っても、ArrayListの場合はそれほど違いはありませんが、LinkedListの方が2番目の方がはるかに効率的です。

22
cletus

それを測定します。すべてのパフォーマンスの質問に対する答えは、VMバージョン、プロセッサ、メモリ速度、キャッシュなどに依存する可能性があります。そのため、特定のプラットフォームで測定する必要があります。

個人的には、意図がより明確であるため、2番目のバリアントを好むでしょう。パフォーマンスが問題になる場合は、とにかく後で最適化できます-そのコードがアプリケーション全体のパフォーマンスにとって本当に重要な場合。

7
Mnementh

LinkedListの場合:

for(ClassOfElement element : listOfElements) {
  System.out.println(element.getValue());
}

以前に回答されました:

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

配列またはRandomAccessコレクションでは、次の操作を行うことで速度をわずかに向上させることができます。

List<Object> list = new ArrayList<Object>();

for (int i=0, d=list.size(); i<d; i++) {
    something(list.get(i));
}

しかし、私は一般的に心配しません。このような最適化は、コードに0.1%以上の違いをもたらすことはありません。コードが実際にどこで時間を費やしているのかを確認するには、Java with -profを呼び出してみてください。

1
gubby

さらに速いのは、fork-joinフレームワークのParallelArrayを使用することです(十分なデータセットがある場合)。

0
akarnokd