プリミティブ配列のモダンforループ
-
06-07-2019 - |
質問
プリミティブ配列のforループ間にパフォーマンスの違いはありますか?
仮定:
double[] doubleArray = new double[300000];
for (double var: doubleArray)
someComplexCalculation(var);
または:
for ( int i = 0, y = doubleArray.length; i < y; i++)
someComplexCalculation(doubleArray[i]);
テスト結果
実際にプロファイルを作成しました:
Total timeused for modern loop= 13269ms
Total timeused for old loop = 15370ms
したがって、少なくとも私のMac OSX JVM 1.5では、モダンループの実際の実行速度が速くなります。
解決
手書きの「古い」 formはより少ない命令を実行し、より高速かもしれませんが、確実に知るために、与えられたJITコンパイラーの下でそれをプロファイルする必要があります。 「新しい」フォームは間違いなくではありません高速です。
逆アセンブルされたコード(SunのJDK 1.5でコンパイルされたコード)を見ると、「新しい」コードが表示されます。フォームは次のコードと同等です:
1: double[] tmp = doubleArray;
2: for (int i = 0, y = tmp.length; i < y; i++) {
3: double var = tmp[i];
4: someComplexCalculation(var);
5: }
つまり、より多くのローカル変数が使用されていることがわかります。 1行目の tmp
への doubleArray
の割り当ては「余分」ですが、ループ内では発生せず、おそらく測定できません。 3行目の var
への割り当ても余分です。パフォーマンスに違いがある場合、これが原因となります。
1行目は不要に思えるかもしれませんが、ループに入る前にメソッドによって配列が計算される場合、結果をキャッシュすることは定型です。
とはいえ、インデックス変数で何かをする必要がない限り、新しいフォームを使用します。パフォーマンスの違いは、実行時にJITコンパイラーによって最適化され、新しい形式がより明確になります。 「手作業」でそれを続けると、将来の最適化を見逃す可能性があります。一般的に、優れたコンパイラーは、「愚かな」を最適化できます。よくコードしますが、「スマート」につまずきますコード。
他のヒント
私の意見では、あなたは知らないし、推測すべきではない。最近のコンパイラを凌toしようとすることは無益です。
人々が「パターン」を学んだことがあります。これにより一部の操作が最適化されたように見えましたが、Javaの次のバージョンでは、これらのパターンは実際には低速でした。
常に可能な限り明確に記述し、実際にユーザー仕様を手に入れて要件を満たさなくなるまで最適化を心配せずに、テストの前後に実行するように注意してください必ず「修正」してください。実際に要件を満たせるように十分に改善しました。
コンパイラは実際に靴下を吹き飛ばす驚くべきことをいくつか行うことができ、いくつかの大きな範囲で反復するテストを行っても、範囲が狭い場合やループ内で起こることを変更すると、まったく異なる動作をする可能性があります。
ジャストインタイムコンパイルは、ときどきCを上回る可能性があることを意味し、場合によっては静的アセンブリ言語を上回ることができない理由はありません(アセンブリは、呼び出しが不要であることを事前に決定できず、Javaが行うことができますそれだけ。
要約すると、コードに入れることができる最大の価値は、読みやすいように記述することです。
自分で測定してみませんか?
これは少し厳しいように聞こえますが、この種の質問は自分で確認するのは非常に簡単です。
配列を作成して、各ループを1000回以上実行し、時間を測定するだけです。グリッチを除去するために数回繰り返します。
違いはありません。 Javaは拡張forを通常のforループに変換します。拡張されたforは、単なる「構文糖」です。生成されるバイトコードは両方のループで同じです。
前回の回答の後でも、あなたの質問に非常に興味を持ちました。だから私も自分でチェックすることにしました。私はこの小さなコードを書きました(数値が素数であるかどうかのチェックについての数学の正確さを無視してください;-)):
public class TestEnhancedFor {
public static void main(String args[]){
new TestEnhancedFor();
}
public TestEnhancedFor(){
int numberOfItems = 100000;
double[] items = getArrayOfItems(numberOfItems);
int repetitions = 0;
long start, end;
do {
start = System.currentTimeMillis();
doNormalFor(items);
end = System.currentTimeMillis();
System.out.printf("Normal For. Repetition %d: %d\n",
repetitions, end-start);
start = System.currentTimeMillis();
doEnhancedFor(items);
end = System.currentTimeMillis();
System.out.printf("Enhanced For. Repetition %d: %d\n\n",
repetitions, end-start);
} while (++repetitions < 5);
}
private double[] getArrayOfItems(int numberOfItems){
double[] items = new double[numberOfItems];
for (int i=0; i < numberOfItems; i++)
items[i] = i;
return items;
}
private void doSomeComplexCalculation(double item){
// check if item is prime number
for (int i = 3; i < item / 2; i+=2){
if ((item / i) == (int) (item / i)) break;
}
}
private void doNormalFor(double[] items){
for (int i = 0; i < items.length; i++)
doSomeComplexCalculation(items[i]);
}
private void doEnhancedFor(double[] items){
for (double item : items)
doSomeComplexCalculation(item);
}
}
アプリを実行すると、次の結果が得られました。
通常。繰り返し0:5594 拡張対象。繰り返し0:5594
通常。繰り返し1:5531 拡張対象。繰り返し1:5547
通常。繰り返し2:5532 拡張対象。繰り返し2:5578
通常。繰り返し3:5531 拡張対象。繰り返し3:5531
通常。繰り返し4:5547 拡張対象。繰り返し4:5532
ご覧のとおり、結果のばらつきは非常に小さく、通常のループはより速く実行され、強化されたループはより高速です。私のコンピューターには他のアプリが開いているので、私はそれが普通だと思います。また、最初の実行のみが他の実行よりも遅くなります-これはJIT最適化に関係していると思います。
平均時間(最初の繰り返しを除く)は、通常のループでは5535,25ミリ秒、拡張ループでは5547ミリ秒です。しかし、両方のループの最適な実行時間は同じ(5531ms)であることがわかります。そのため、両方のループのパフォーマンスは同じであり、経過時間の変動は他のアプリケーションによるものであると結論付けられると思います(さらにはマシンのOS)。