使用Java Long包装器与原始肺添加数字

时间:2012-05-27 05:11:32

标签: java performance object profiling primitive

我正在运行此代码并获得意外结果。我希望添加基元的循环执行得更快,但结果不一致。

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 使用多头 1842ms 使用Longs 614ms

我尝试在自己的java程序中运行每个单独的测试,但结果是一样的。什么可能导致这种情况?

小细节:运行java 1.6

编辑: 我让另外两个人尝试这个代码,一个得到的结果与我得到的结果完全相同。另一个获得实际有意义的结果!我问那些得到正常结果的人给我们他的班级二进制文件。我们运行它,我们仍然得到奇怪的结果。问题不在编译时(我认为)。我正在运行1.6.0_31,得到正常结果的人是1.6.0_16,那个像我一样得到奇怪结果的人是在1.7.0_04。

编辑:在程序开始时使用Thread.sleep(5000)获得相同的结果。在整个程序中使用while循环也可以得到相同的结果(看看java完全启动后的时间是否会收敛到正常时间)

3 个答案:

答案 0 :(得分:3)

我怀疑这是JVM的预热效果。具体来说,代码在某些时候正在编译JIT,这会扭曲您所看到的时间。

将整个批次放在循环中,忽略报告的时间直到它们稳定下来。 (但请注意,它们不会完全稳定。垃圾正在生成,因此GC需要偶尔启动。这可能会导致时间扭曲,至少有一点。解决这个问题的最佳方法是运行外循环的大量迭代,并计算/显示平均时间。)

另一个问题是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

请注意,前两列非常稳定,但第三列在第3次迭代时显示出显着的加速...可能表明已经发生了JIT编译。

有趣的是,在我将测试分成单独的方法之前,我没有看到第3次迭代的加速。这些数字看起来都像前两行。这似乎是说JVM(我正在使用)不会JIT编译当前正在执行的方法......或类似的东西。

无论如何,这表明(对我来说)应该有预热效果。如果你没有看到预热效果,那么你的基准测试正在做一些禁止JIT编译的事情......因此对实际应用程序没有意义。

答案 1 :(得分:0)

我也很惊讶。

我的第一个猜测是无意中“autoboxing”,但这显然不是您示例代码中的问题。

此链接可能提供线索:

  

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

     

的valueOf

     

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”)来花费更多时间和更多空间。我期望使用包装器三次 FASTER

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

附录:

我的代码得到了这些结果:

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

我在一个32位的单核CPU上运行JDK 1.6.0_16。

答案 2 :(得分:0)

好的 - 这是一个略有不同的版本,以及我的结果(运行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. “long”primtive始终相当于“no-op”

  3. “长”包装器持续多得多,慢得多

  4. “new Long()”比“Long.valueOf()”

  5. 稍快
  6. 将循环计数器从“int”更改为“long”会使前两列(“base”和“longs”很多更慢。

  7. “JIT热身”在前几次迭代后可以忽略不计......

  8. ... 提供 I / O(如System.out),可能内存密集型活动(如StringBuilder)移动在实际测试之外部分。