58

アプリケーションでforループをfor-eachループに更新しているときに、次のような「パターン」に出くわしました。

for (int i = 0, n = a.length; i < n; i++) {
    ...
}

それ以外の

for (int i = 0; i < a.length; i++) {
    ...
}

ループごとにsize()メソッドを呼び出す必要がないため、コレクションのパフォーマンスが向上することがわかります。しかし、配列では??

そこで、疑問が生じました。array.length通常の変数よりも高価ですか?

4

8 に答える 8

78

いいえ、array.lengthisO(1)または定数時間演算の呼び出しです。

.lengthis(のように動作する)はのpublic finalメンバーであるためarray、アクセスはローカル変数よりも遅くはありません。(これは、のようなメソッドの呼び出しとは大きく異なりますsize()

最新のJITコンパイラは、.lengthとにかくすぐに実行するための呼び出しを最適化する可能性があります。

これを確認するには、OpenJDKのJITコンパイラのソースコードを確認するか、JVMにJITでコンパイルされたネイティブコードをダンプしてコードを調べます。

JITコンパイラがこれを実行できない場合があることに注意してください。例えば

  1. 囲みメソッドをデバッグしている場合、または
  2. ループ本体にレジスタのスピルを強制するのに十分なローカル変数がある場合。
于 2009-07-30T18:12:01.147 に答える
19

私は昼食をとりながら少し時間を過ごしました:

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

ノート:

  1. テストを逆にすると、おそらくガベージ コレクション (?) が原因で、約半分n = a.lengthよりも高速であることが示されます。i < a.length
  2. 250000000でたのであまり大きくできませんでしOutOfMemoryError270000000

要点は、これは他の誰もが作成しているものであり、メモリ不足で Java を実行する必要がありますが、それでも 2 つの選択肢の速度に大きな違いは見られません。実際に重要なことに開発時間を費やしてください。

于 2009-07-30T18:39:50.577 に答える
11

大きな違いがあるとは思えません。たとえあったとしても、おそらくコンパイル中に最適化されていると思います。そのようなことをマイクロ最適化しようとすると、時間を無駄にしていることになります。最初にコードを読みやすく修正してから、パフォーマンスに問題がある場合はプロファイラーを使用し、必要に応じてより適切なデータ構造/アルゴリズムを選択してからプロファイラーが強調表示する部分を最適化することを検討してください。

于 2009-07-30T18:13:08.650 に答える
7

配列の長さは、Java では配列のメンバー変数 (要素と同じではない) として格納されるため、その長さのフェッチは一定時間の操作であり、通常のクラスからメンバー変数を読み取るのと同じです。C や C++ などの多くの古い言語では、配列の一部として長さが格納されないため、ループが開始する前に長さを格納する必要があります。Java でそれを行う必要はありません。

于 2009-07-30T18:19:43.223 に答える
4

その場合、逆ループを作成してみませんか。

for (int i = a.length - 1; i >= 0; --i) {
    ...
}

ここには 2 つのマイクロ最適化があります。

  • ループ反転
  • 後置デクリメント
于 2009-07-31T17:33:51.550 に答える
3

変数に格納する方が、これまでになく少し速くなる可能性があります。しかし、プロファイラーがそれを問題として指摘した場合、私は非常に驚きます。

バイトコードレベルでは、配列の長さの取得はarraylengthバイトコードを使用して行われます。バイトコードより遅いかどうかはわかりませんがiload、気付くほどの違いはないはずです。

于 2009-07-30T18:11:37.123 に答える
2

この回答は 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 つすべてが同じネイティブ コードを生成すると予想されます。

ただし、最初の形式の方が間違いなく読みやすいので、それを使用することをお勧めします。

于 2009-07-30T18:24:03.807 に答える
1

Array.lengthは定数であり、JITコンパイラは両方のインスタンスでそれを確認する必要があります。結果として得られるマシンコードは、どちらの場合も同じであると思います。少なくともサーバーコンパイラについては。

于 2009-07-30T19:47:02.877 に答える