アプリケーションでforループをfor-eachループに更新しているときに、次のような「パターン」がたくさん見つかりました。
for (int i = 0, n = a.length; i < n; i++) {
...
}
の代わりに
for (int i = 0; i < a.length; i++) {
...
}
ループごとにsize()メソッドを呼び出す必要がないため、コレクションのパフォーマンスが向上することがわかります。しかし、配列では??
それで質問が起こりました:はarray.length
通常の変数よりも高価ですか?
いいえ、_array.length
_の呼び出しはO(1)
または定数時間操作です。
_.length
_はpublic
final
のarray
メンバーであるため、ローカル変数よりもアクセスに時間がかかりません。 (これは、size()
のようなメソッドの呼び出しとは大きく異なります)
最新のJITコンパイラは、とにかく_.length
_への呼び出しを最適化する可能性があります。
これを確認するには、OpenJDKのJITコンパイラのソースコードを確認するか、JVMにJITでコンパイルされたネイティブコードをダンプさせてコードを調べます。
JITコンパイラがこれを実行できない場合があることに注意してください。例えば.
私は昼食に少し時間を過ごしました:
public static void main(String[] args) {
final int[] a = new int[250000000];
long t;
for (int j = 0; j < 10; j++) {
t = System.currentTimeMillis();
for (int i = 0, n = a.length; i < n; i++) { int x = a[i]; }
System.out.println("n = a.length: " + (System.currentTimeMillis() - t));
t = System.currentTimeMillis();
for (int i = 0; i < a.length; i++) { int x = a[i]; }
System.out.println("i < a.length: " + (System.currentTimeMillis() - t));
}
}
結果:
n = a.length: 672
i < a.length: 516
n = a.length: 640
i < a.length: 516
n = a.length: 656
i < a.length: 516
n = a.length: 656
i < a.length: 516
n = a.length: 640
i < a.length: 532
n = a.length: 640
i < a.length: 531
n = a.length: 641
i < a.length: 516
n = a.length: 656
i < a.length: 531
n = a.length: 656
i < a.length: 516
n = a.length: 656
i < a.length: 516
ノート:
n = a.length
がi < a.length
よりも約半分速いことが示されます。250000000
でOutOfMemoryError
を取得したため、270000000
をこれ以上大きくすることはできませんでした。重要なのは、それは他の誰もが作っているものであり、メモリ不足でJavaを実行する必要がありますが、2つの選択肢の間で速度に大きな違いは見られません。実際に重要なことに関する開発時間。
大きな違いがあるとは思えません。たとえあったとしても、コンパイル中に最適化されていると思います。そのようなことをマイクロ最適化しようとすると、時間を無駄にしていることになります。最初にコードを読みやすく修正してから、パフォーマンスに問題がある場合はprofilerを使用し、必要に応じてより適切なデータ構造/アルゴリズムを選択することを心配します。then最適化について心配します。プロファイラーが強調表示する部分。
配列の長さは、Javaでは配列のメンバー変数(要素と同じではありません)として格納されるため、その長さのフェッチは、通常のクラスからメンバー変数を読み取るのと同じように、定数時間の操作です。 CやC++などの古い言語の多くは、長さを配列の一部として格納しないため、ループが開始する前に長さを格納する必要があります。 Javaでそれを行う必要はありません。
その場合は、逆ループを作成してみませんか。
for (int i = a.length - 1; i >= 0; --i) {
...
}
ここには2つのマイクロ最適化があります。
変数に格納する方が、これまでになく少し速くなる可能性があります。しかし、プロファイラーがそれを問題として指摘した場合、私は非常に驚きます。
バイトコードレベルでは、配列の長さの取得はarraylength
バイトコードを使用して行われます。 iload
バイトコードより遅いかどうかはわかりませんが、気付くほどの違いはないはずです。
この答えはC#の観点からのものですが、Javaにも同じことが当てはまると思います。
C#では、イディオム
for (int i = 0; i < a.length; i++) { ...}
は配列を反復処理するものとして認識されるため、各配列アクセスではなく、ループ内の配列にアクセスするときに境界チェックが回避されます。
これは、次のようなコードで認識される場合と認識されない場合があります。
for (int i = 0, n = a.length; i < n; i++) { ...}
または
n = a.length;
for (int i = 0; i < n; i++) { ...}
この最適化のどれだけがコンパイラーとJITterによって実行されるかはわかりません。特に、JITterによって実行される場合は、3つすべてが同じネイティブコードを生成すると予想されます。
ただし、最初の形式は、間違いなく人々が読みやすいので、それを使用すると思います。
Array.lengthは定数であり、JITコンパイラは両方のインスタンスでそれを確認する必要があります。結果のマシンコードはどちらの場合も同じであると思います。少なくともサーバーコンパイラについては。