高速化 - java 拡張 for 文 高速 化
forループとfor-eachループのパフォーマンスに違いはありますか? (11)
Android開発チームの違いを簡単に分析してみましょう。
https://www.youtube.com/watch?v=MZOf3pOAM6A
その結果、違いがあり、非常に大きなリストを持つ非常に拘束された環境では、目立つ違いが生じる可能性があります。 彼らのテストでは、各ループに対して2倍の時間がかかりました。 しかし、彼らのテストは、400,000の整数のarraylist上にあった。 アレイ内の要素当たりの実際の差は6 マイクロ秒であった 。 私はテストしていないと言っていませんが、プリミティブではなくオブジェクトを使って少し違いがあると思いますが、あなたが尋ねられる尺度がわからないところでライブラリコードをビルドしない限りそれを繰り返すには、その違いが強調する価値はないと私は思う。
次の2つのループのパフォーマンスの違いは何か(あれば)ですか?
for (Object o: objectArrayList) {
o.DoSomething();
}
そして
for (int i=0; i<objectArrayList.size(); i++) {
objectArrayList.get(i).DoSomething();
}
ArrayListやVectorのようなものであっても、 "get"は単純な配列検索であり、2番目のループはまだ最初のものには付加的なオーバーヘッドがあります。 私はそれが最初のものよりも少し遅いと思っています。
for-eachループは、一般に好ましいものでなければならない。 使用しているList実装がランダムアクセスをサポートしていない場合、「get」アプローチは遅くなる可能性があります。 たとえば、LinkedListを使用すると、トラバーサルコストが発生しますが、for-eachアプローチではリスト内の位置を追跡するイテレータが使用されます。 for-eachループのニュアンスについて詳しくは、
私は記事がここにあると思う: 新しい場所
ここに示されたリンクは死んでいた。
foreachはコードの意図をより明確にし、通常は非常にマイナーなスピードの改善より優先されます。
インデックスを付けられたループが見えるときはいつでも、私はそれが何をするかを確かめるためにもう少し解析する必要があります。例えば、ゼロから開始しますか?エンドポイントを含めたり除外したりしますか?
私の時間の大部分は、コードを読むこと(私が書いたものや他の誰かが書いたもの)に費やされているようで、透明性はパフォーマンスよりも常に重要です。 ホットスポットがこのような驚くべき仕事をするので、今日は簡単にパフォーマンスを却下することができます。
さて、パフォーマンスへの影響はほとんど重要ではありませんが、ゼロではありません。 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ループはiteratorでバージョンを使用しているので、 ArrayList
の場合、for-eachループは最速ではありません。
はい、 for-each
variantは、通常のindex-based-for-loop
より高速です。
for-each
variantはiterator
使用しiterator
。 したがって、トラバーシングは、索引ベースの通常のfor
ループよりも高速です。
これは、 iterator
が次の要素の直前と直前の要素の直後を指しているので、 iterator
がトラバースに最適化されているためです。 index-based-for-loop
を遅くする理由の1つは、 iterator
ではないたびに要素位置に計算して移動する必要があることです。
受け入れられた答えは、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のあるデバイスでは()と区別がつきません。 これは、Javaプログラミング言語のバージョン1.5で導入された拡張forループ構文を使用します。
したがって、デフォルトでenhanced forループを使用する必要がありますが、パフォーマンス重視のArrayList繰り返しの場合は手作業でカウントされたループを考慮する必要があります。
変数名objectArrayList
によって、それがjava.util.ArrayList
インスタンスであると仮定します。 その場合、パフォーマンスの差は目立たないでしょう。
一方、 java.util.LinkedList
インスタンスの場合、 List#get(int)
がO(n)操作であるため、2番目の方法ははるかに遅くなります。
ループ内のロジックが索引を必要としない限り、最初の方法が常に優先されます。
残念ながら違いがあるようです。
両方の種類のループで生成されたバイトコードを見ると、それらは異なります。
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
Oracleとは何ですか?
私はWindows 7でJava 7と8でこれを試しました。
確かに知る唯一の方法は、それをベンチマークすることです。それでも、 それは聞こえるほどシンプルではありません 。 JITコンパイラは、あなたのコードに非常に予期せぬことをすることができます。
1. for(Object o: objectArrayList){
o.DoSomthing();
}
and
2. for(int i=0; i<objectArrayList.size(); i++){
objectArrayList.get(i).DoSomthing();
}
どちらも同じですが、それぞれのプログラムを簡単に安全に使用するためには、2番目の使用方法でエラーが発生する可能性があります。