6

このコードを実行すると、予期しない結果が得られます。プリミティブを追加するループははるかに高速に実行されると期待していますが、結果は一致しません。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        StringBuilder output = new StringBuilder();
        long start = System.currentTimeMillis();
        long limit = 1000000000; //10^9
        long value = 0;
        for(long i = 0; i < limit; ++i){}
        long i;
        output.append("Base time\n");
        output.append(System.currentTimeMillis() - start + "ms\n");
        start = System.currentTimeMillis();
        for(long j = 0; j < limit; ++j) {
            value = value + j;
        }
        output.append("Using longs\n");
        output.append(System.currentTimeMillis() - start + "ms\n");
        start = System.currentTimeMillis();
        value = 0;
        for(long k = 0; k < limit; ++k) {
            value = value + (new Long(k));
        }
        output.append("Using Longs\n");
        output.append(System.currentTimeMillis() - start + "ms\n");
        System.out.print(output);
    }
}

出力:

基本時間 359ms long を使用 1842ms Long を使用 614ms

独自の Java プログラムで個々のテストを実行しようとしましたが、結果は同じです。何が原因でしょうか?

詳細: Java 1.6 の実行

編集: 他の 2 人にこのコードを試すように依頼しましたが、1 人は私とまったく同じ奇妙な結果を得ました。もう 1 つは、実際に意味のある結果が得られます。私は、正常な結果を得た人に、彼のクラス バイナリを提供するように依頼しました。それを実行しても、まだ奇妙な結果が得られます。問題はコンパイル時ではありません(私は思います)。私は 1.6.0_31 を実行しています。正常な結果が得られるのは 1.6.0_16 で、私のように奇妙な結果が得られるのは 1.7.0_04 です。

編集: プログラムの開始時に Thread.sleep(5000) で同じ結果を取得します。また、プログラム全体を while ループしても同じ結果が得られます (Java が完全に起動された後、時間が通常の時間に収束するかどうかを確認するため)。

4

3 に答える 3

3

これはJVMのウォームアップ効果だと思います。具体的には、コードはある時点でJITコンパイルされており、これが表示されている時間を歪めています。

ロット全体をループに入れ、安定するまで報告された時間を無視します。(ただし、完全に安定するわけではないことに注意してください。ガベージが生成されているため、GCがときどきキックする必要があります。これにより、タイミングが少なくとも少し歪む可能性があります。これに対処する最善の方法は、外側のループの膨大な数の反復、および平均時間を計算/表示します。)

もう1つの問題は、Javaの一部のリリースのJITコンパイラーが、テストしようとしているものを最適化できる可能性があることです。

  • オブジェクトの作成と即時のアンボックスLong化を最適化できることがわかります。(ルイに感謝します!)

  • ループが「忙しい作業」を行っていることを理解し、それらを完全に最適化することができます。(の値は、value各ループが終了すると使用されません。)


FWIW、前者はキャッシュされたインスタンスを利用できるため、一般Long.valueOf(long)的には使用することをお勧めします。ただし、この場合、最初の数回のループ反復を除くすべてでキャッシュミスが発生することが予測できるため、この推奨事項は役に立ちません。どちらかといえば、問題のループが遅くなる可能性があります。new Long(long)Long


アップデート

私は自分で調査を行ったところ、次のようになりました。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        while (true) {
        test();
        }
    }

    private static void test() {
        long start = System.currentTimeMillis();
        long limit = 10000000; //10^9
        long value = 0;
        for(long i = 0; i < limit; ++i){}
        long t1 = System.currentTimeMillis() - start;
        start = System.currentTimeMillis();
        for(long j = 0; j < limit; ++j) {
            value = value + j;
        }
        long t2 = System.currentTimeMillis() - start;
        start = System.currentTimeMillis();
        for(long k = 0; k < limit; ++k) {
            value = value + (new Long(k));
        }
        long t3 = System.currentTimeMillis() - start;
        System.out.print(t1 + " " + t2 + " " + t3 + " " + value + "\n");
    }
}

これにより、次の出力が得られました。

28 58 2220 99999990000000
40 58 2182 99999990000000
36 49 157 99999990000000
34 51 157 99999990000000
37 49 158 99999990000000
33 52 158 99999990000000
33 50 159 99999990000000
33 54 159 99999990000000
35 52 159 99999990000000
33 52 159 99999990000000
31 50 157 99999990000000
34 51 156 99999990000000
33 50 159 99999990000000

最初の2つの列はかなり安定していますが、3番目の列は3回目の反復で大幅な高速化を示しています...おそらくJITコンパイルが発生したことを示しています。

興味深いことに、テストを別のメソッドに分割する前は、3回目の反復でスピードアップが見られませんでした。数字はすべて最初の2行のように見えました。そして、それは、JVM(私が使用している)が現在実行中のメソッドをJITコンパイルしないことを言っているようです...またはそのようなもの。

とにかく、これは(私には)ウォームアップ効果があるはずであることを示しています。ウォームアップ効果が見られない場合、ベンチマークはJITコンパイルを阻害していることを実行しているため、実際のアプリケーションには意味がありません。

于 2012-05-27T05:27:23.580 に答える
0

私もびっくりしました。

私の最初の推測は、不注意な「自動ボクシング」だったでしょうが、それは明らかにあなたのサンプルコードの問題ではありません。

このリンクは手がかりを与えるかもしれません:

http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Long.html

public static Long valueOf(long l)

指定されたlong値を表すLongインスタンスを返します。新しいLongインスタンスが必要ない場合は、コンストラクターLong(long)よりもこのメソッドを使用する必要があります。このメソッドは、頻繁に要求される値をキャッシュすることで、スペースと時間のパフォーマンスを大幅に向上させる可能性があります。

Parameters:
    l - a long value. 

Returns:
    a Long instance representing l.

Since:
    1.5

しかし、はい、ラッパー(「Long」など)を使用すると、より多くの時間とスペースが必要になると思います。ラッパーの使用が3倍速くなるとは思いません

================================================== ==============================

補遺:

私はあなたのコードでこれらの結果を得ました:

Base time     6878ms
Using longs  10515ms
Using Longs 428022ms

ポーキーな32ビットのシングルコアCPUでJDK1.6.0_16を実行しています。

于 2012-05-27T05:20:07.790 に答える
0

OK-これが私の結果(JDK 1.6.0_16 pokey 32ビットシングルコードCPUを実行している)と一緒に少し異なるバージョンです:

import java.util.*;

/*
Test      Base   longs  Longs/new  Longs/valueOf
----      ----   -----  ---------  -------------
   0       343     896       3431           6025
   1       342     957       3401           5796
   2       342     881       3379           5742
*/

public class LongTest {

    private static int limit = 100000000;
    private static int ntimes = 3;
    private static final long[] base = new long[ntimes];
    private static final long[] primitives = new long[ntimes];
    private static final long[] wrappers1 = new long[ntimes];
    private static final long[] wrappers2 = new long[ntimes];

    private static void test_base (int idx) {
       long start = System.currentTimeMillis();
       for (int i = 0; i < limit; ++i){}
       base[idx] = System.currentTimeMillis() - start;
    }

    private static void test_primitive (int idx) {
       long value = 0;
       long start = System.currentTimeMillis();
       for (int i = 0; i < limit; ++i){
         value = value + i;
       }
       primitives[idx] = System.currentTimeMillis() - start;
    }

    private static void test_wrappers1 (int idx) {
       long value = 0;
       long start = System.currentTimeMillis();
       for (int i = 0; i < limit; ++i){
         value = value + new Long(i);
       }
       wrappers1[idx] = System.currentTimeMillis() - start;
    }

    private static void test_wrappers2 (int idx) {
       long value = 0;
       long start = System.currentTimeMillis();
       for (int i = 0; i < limit; ++i){
         value = value + Long.valueOf(i);
       }
       wrappers2[idx] = System.currentTimeMillis() - start;
    }

    public static void main(String[] args) {

      for (int i=0; i < ntimes; i++) {
         test_base (i);
            test_primitive(i);
            test_wrappers1 (i);
            test_wrappers2 (i);
      }

      System.out.println ("Test      Base   longs  Longs/new  Longs/valueOf");
      System.out.println ("----      ----   -----  ---------  -------------");
      for (int i=0; i < ntimes; i++) {
         System.out.printf ("  %2d    %6d  %6d     %6d         %6d\n",
            i, base[i], primitives[i], wrappers1[i], wrappers2[i]);
       }
    }
}

================================================== =====================

2012年5月28日:

Windows 7/64を実行し、同じJDKリビジョン1.6.0_16を実行しているより高速な(ただしまだ控えめな)デュアルコアCPUからの追加のタイミングを次に示します。

/*
PC 1: limit = 100,000,000, ntimes = 3, JDK 1.6.0_16 (32-bit):
Test      Base   longs  Longs/new  Longs/valueOf
----      ----   -----  ---------  -------------
   0       343     896       3431           6025
   1       342     957       3401           5796
   2       342     881       3379           5742

PC 2: limit = 1,000,000,000, ntimes = 5,JDK 1.6.0_16 (64-bit):
Test      Base   longs  Longs/new  Longs/valueOf
----      ----   -----  ---------  -------------
   0         3       2       5627           5573
   1         0       0       5494           5537
   2         0       0       5475           5530
   3         0       0       5477           5505
   4         0       0       5487           5508

PC 2: "for loop" counters => long; limit = 10,000,000,000, ntimes = 5:
Test      Base   longs  Longs/new  Longs/valueOf
----      ----   -----  ---------  -------------
   0      6278    6302      53713          54064
   1      6273    6286      53547          53999
   2      6273    6294      53606          53986
   3      6274    6325      53593          53938
   4      6274    6279      53566          53974
*/

あなたは気づくでしょう:

  1. 私はStringBuilderを使用しておらず、プログラムが終了するまですべてのI/Oを分離しています。

  2. 「長い」プリミティブは、一貫して「ノーオペレーション」と同等です。

  3. 「長い」ラッパーは一貫してはるかに遅く、はるかに遅い

  4. 「newLong()」は「Long.valueOf()」よりわずかに高速です

  5. ループカウンターを「int」から「long」に変更すると、最初の2つの列(「base」と「longs」)がはるかに遅くなります。

  6. 「JITウォームアップ」は、最初の数回の反復後は無視できます...

  7. ... I / O(System.outなど)およびメモリを大量に消費する可能性のあるアクティビティ(StringBuilderなど)が実際のテストセクションの外に移動さ場合。

于 2012-05-27T06:20:35.157 に答える