Javaでは、通常、次のようなforループを作成します。
for (int i = 0; i < max; i++) {
something
}
しかし最近、同僚がそれを次のように入力しました。
for (int i = 0; i < max; ++i) {
something
}
彼は後者の方が速いだろうと言った。本当?
Javaでは、通常、次のようなforループを作成します。
for (int i = 0; i < max; i++) {
something
}
しかし最近、同僚がそれを次のように入力しました。
for (int i = 0; i < max; ++i) {
something
}
彼は後者の方が速いだろうと言った。本当?
いいえ、それは真実ではありません。多数の反復の各ループのタイミングを調整することでパフォーマンスを測定できますが、それらは同じになると確信しています。
神話はCから来たもので、前者はiをインクリメントしてから返すことで実装できるため、++i
より高速であると見なされていました。i++
後者は、iの値を一時変数にコピーし、iをインクリメントしてから、一時変数を返すことで実装できます。最初のバージョンは一時的なコピーを作成する必要がないため、多くの人がそれがより高速であると考えています。ただし、式がステートメントとして使用されている場合、最新のCコンパイラーは一時コピーを最適化して、実際には違いがないようにすることができます。
この質問には、Javaバイトコードが必要でした。次のコードを検討してください。
public class PostPre {
public static void main(String args[]) {
int n = 5;
loop1(n);
loop2(n);
}
public static void loop1(int n) {
for (int i = 0; i < n; i++) {}
}
public static void loop2(int n) {
for (int i = 0; i < n; ++i) {}
}
}
次に、コンパイルして逆アセンブルします。
$ javac PostPre.java; javap -c PostPre.class
Compiled from "PostPre.java"
public class PostPre {
public PostPre();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_5
1: istore_1
2: iload_1
3: invokestatic #2 // Method loop1:(I)V
6: iload_1
7: invokestatic #3 // Method loop2:(I)V
10: return
public static void loop1(int);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iload_0
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
public static void loop2(int);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: iload_0
4: if_icmpge 13
7: iinc 1, 1
10: goto 2
13: return
}
loop1()
とloop2()
同じバイトコードを持っています。
適度に機能するオプティマイザの場合、それらはまったく同じになります。よくわからない場合は、出力バイトコードを確認するか、プロファイルを作成してください。
たとえそうだとしても、私は非常に疑わしいですが、あなたの同僚は、ループ式を最適化する方法よりも、学習に時間を費やす方が本当に良いはずです。
ご使用の環境でこれを試してください
public class IsOptmized {
public static void main(String[] args) {
long foo; //make sure the value of i is used inside the loop
long now = 0;
long prefix = 0;
long postfix = 0;
for (;;) {
foo = 0;
now = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
foo += i;
}
postfix = System.currentTimeMillis() - now;
foo = 0;
now = System.currentTimeMillis();
for (int i = 0; i < 1000000000; ++i) {
foo += i;
}
prefix = System.currentTimeMillis() - now;
System.out.println("i++ " + postfix + " ++i " + prefix + " foo " + foo);
}
}
}
私は私にくれます
i++ 1690 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1611 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1600 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1692 ++i 1610 foo 499999999500000000
i++ 1701 ++i 1610 foo 499999999500000000
i++ 1691 ++i 1610 foo 499999999500000000
ですから、それほど多くなくても、違いがあると思います
これ以上速くなることはありません。コンパイラーとJITを備えたJVMは、そのような重要でない違いのミンスミートを作ります。
必要に応じて、通常のループ最適化手法を使用して、展開などの速度の利点を得ることができます。
いいえ、まったく違いはありません。
これはC++からのものですが、この場合でもまったく違いはありません。違いがあるのは、iがオブジェクトであるところです。i ++はアイテムの元の変更されていない値を返す必要があるため、オブジェクトの追加コピーを作成する必要がありますが、++ iは変更されたオブジェクトを返すことができるため、コピーを保存します。
ユーザー定義オブジェクトを使用するC++では、コピーのコストがかなり高くなる可能性があるため、覚えておく価値があります。そして、このため、とにかく同じくらい良いので、人々はint変数にもそれを使用する傾向があります...
「javap-cYourClassName」で逆コンパイルし、結果を確認して決定します。このようにして、コンパイラが実際に何をしているのかを確認できます。あなたが思っていることではありません。このようにして、一方の方法がもう一方の方法よりも速い理由もわかります。
Javaでは違いはありません。increment式の結果が直接使用されていないため、最近のコンパイラ*はどちらの場合も同じバイトコード()を生成する必要があります。
3番目のオプションがありますが、それでも同じバイトコードです*: iinc
for (int i = 0; i < max; i += 1) {
something
}
* Eclipseのコンパイラでテスト済み
Javaではそのような違いはありません。Javaマシンはコードを解釈し、++iまたはi++のどちらを記述しても、まったく同じ命令セットのバイトコードに変換されます。
ただし、C / C ++には大きな違いがあり、最適化フラグを使用していない場合、ループは最大3倍遅くなる可能性があります。
-O / -O3などの最適化フラグを使用すると、コンパイラーは出力アセンブリコードをより単純にし(ほとんどの場合)、したがってより高速にします(ほとんどの場合)。
それでももっと速くても、HotSpotの時代には誰も気にしません。JITが最初に行うことは、javacが行ったすべての最適化を削除することです。その後、すべてをJITに任せて高速化します。