次の2つのループのパフォーマンスの違いは何ですか?
for (Object o: objectArrayList) {
o.DoSomething();
}
そして
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
次の2つのループのパフォーマンスの違いは何ですか?
for (Object o: objectArrayList) {
o.DoSomething();
}
そして
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
回答:
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)、プログラマーは必ずしもそうするわけではありません。
これらのループはすべてまったく同じです。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ループを変更する前と後の両方で、この奇妙なマイクロ最適化されたものにソフトウェアのベンチマークを行うことを忘れないでください。
get(int)
、もう1つはを使用しIterator
ます。n回実行しているためLinkedList
、パフォーマンスfor(int i=0;i<strings.size();i++) { /* do something using strings.get(i) */ }
がはるかに悪い場所を検討してくださいget(int)
。
for-eachループが一般的に推奨されます。使用しているList実装がランダムアクセスをサポートしていない場合、「取得」アプローチは遅くなる可能性があります。たとえば、LinkedListを使用すると、トラバーサルコストが発生しますが、for-eachアプローチでは、リスト内での位置を追跡する反復子を使用します。for-eachループのニュアンスに関する詳細情報。
記事は今ここにあると思います: 新しい場所
ここに表示されているリンクは無効になっています。
まあ、パフォーマンスへの影響はほとんどありませんが、ゼロではありません。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ループは最速ではありません。
残念ながら違いがあるようです。
両方の種類のループで生成されたバイトコードを見ると、ループは異なります。
これは、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でこれを試しました。
次のコード:
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ループで明示的または暗黙的に)可読性を維持しながらパフォーマンスの低下を回避できます。
"get"が単純な配列ルックアップであるArrayListやVectorのようなものでも、2番目のループには、最初のループにはない追加のオーバーヘッドがあります。最初よりも少し遅いと思います。
確実に知る唯一の方法はそれをベンチマークすることです、そしてそれさえそれが聞こえるかもしれないほど単純ではありません。JITコンパイラーは、コードに対して予期しないことを行う可能性があります。
以下は、Android開発チームが出した違いの簡単な分析です。
https://www.youtube.com/watch?v=MZOf3pOAM6A
結果があるということである差は、非常に大規模なリストと非常に控えめな環境でそれは顕著な違いである可能性があります。テストでは、for eachループに2倍の時間がかかりました。ただし、テストは400,000の整数の配列リストを超えていました。配列の要素ごとの実際の差は6 マイクロ秒でした。私はテストしていませんし、彼らも言っていませんが、プリミティブではなくオブジェクトを使用すると、差がわずかに大きくなると思いますが、それでも、要求されるものの規模がわからないライブラリコードを構築している場合を除きます繰り返しますが、違いを強調する価値はないと思います。
受け入れられた回答は、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の反復には手書きのカウントループを検討してください。