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

6 java performance primitive profiling object

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

基准时间359ms使用长1842ms使用长614ms

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

小细节:运行java 1.6

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

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

Ste*_*n C 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

这给了我以下输出。

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
Run Code Online (Sandbox Code Playgroud)

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

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

无论如何,这(对我来说)表明应该有一个热身效果。如果您没有看到预热效果,则说明您的基准测试正在执行一些抑制 JIT 编译的操作……因此对于实际应用程序来说没有意义。