为什么test1()的运行速度比test2()快得多?

时间:2014-06-21 16:11:06

标签: java performance profile

import java.util.Random;


public class Test{
    static int r = new Random().nextInt(2);
    static int a(){
        return r==1 ? 1 :0;
    }

    public static void test1() throws  Exception {
        //
        System.out.println(1403187139018L);
        for (int i = 0; i <   1073741824; i++) {}//*

        // Thread.sleep(20000);
        long d = 0;

        for (int j = 0; j < 10; j++) {
            long y = System.currentTimeMillis();

            for (int x = 0; x < 1073741823; x++) {
                d += r==0?1:0;
            }
            System.out.println((System.currentTimeMillis() -y));
        }
    }

    public static void test2()  throws  Exception{

        // Thread.sleep(20000);
        long d = 0;

        for (int j = 0; j < 10; j++) {
            long y = System.currentTimeMillis();

            for (int x = 0; x < 1073741824; x++) {
                d += r==0?1:0;
            }
            System.out.println((System.currentTimeMillis() -y));

            // System.out.println("time:"+ (System.currentTimeMillis() - y));
        }
    }

    public static void main(String[] args) throws  Exception{
        // Thread.sleep(20000);

        test1();
        test2();

    }

}

当我运行上面的代码时,我得到了这个输出:

32
26
28
28
32
29
35
33
30
31
1321
1308
1324
1277
1348
1321
1337
1413
1287
1331

为什么test1要快得多?

除了以下内容之外没有区别:

System.out.println(1403187139018L);
for (int i = 0; i <   1073741824; i++) {}//*

此外,test1的时间成本是25-35毫秒,我觉得难以置信。我在C中编写了相同的代码,每个for循环运行大约需要4秒钟。

这种行为似乎很奇怪。我如何知道何时添加:

System.out.println(1403187139018L);
for (int i = 0; i <   1073741824; i++) {}//*

另外,如果我改变

r==0?1:0

a()

然后test2()的运行速度比test1()快。

我得到的输出是:

1403187139018
3726
3729
3619
3602
3797
4362
4498
3816
4143
4368
1673
1386
1388
1323
1296
1337
1294
1283
1235
1460

原始遗留代码: ...

long t = System.currentTimeMillis();
MappedByteBuffer mbb = map(new File("temp.mmp"), 1024L * 1024 * 1024);

System.out.println("load " + (System.currentTimeMillis() - t));//*
for (int i = 0; i < 2014L * 1024 * 1024; i++) {}//*
int d = 0;
for (int j = 0; j < 10; j++) {
    t = System.currentTimeMillis();
    mbb.position(0);
    mbb.limit(mbb.capacity());

    for (int i = 0; i < mbb.capacity(); i++) {
        d += mbb.get();
    }

    ....
}
System.out.println(d);

3 个答案:

答案 0 :(得分:5)

空循环可能在第一种方法中触发JIT编译。而且JIT足够智能,可以意识到除了获取当前时间和打印时间差异之外,您的代码不会做任何有用的事情。因此,它根本不运行它来优化无用的代码。

如果您编写实际有用的代码,JIT将做正确的事情。不要试图通过添加空循环来搞乱它。

答案 1 :(得分:1)

影响JIT编辑的因素太多了:

  1. 执行统计。解释器运行一个方法时,它会收集不同的统计信息:执行哪些路径,采用哪些分支,看到哪些类实例等。在test1中,统计信息是在第一个(空)循环内收集的,因此欺骗了JIT编译器关于真实的执行情况。
  2. 类初始化和不常见的陷阱。从System.out.println中删除第一个test1时,并非所有与打印相关的类都已初始化。尝试调用未初始化类的方法会导致不常见的陷阱导致使用新知识进行去优化和进一步重新编译方法。
  3. 使用test1替换r==0?1:0时,a()中收集的错误统计信息会出现错误的笑话。在编译的test1中,方法a()之前从未执行过,因此没有机会进行优化。这就是为什么它比使用test2知识编译的a()慢的原因。
  4. 当然,在尝试从头开始编写微基准时,很难预测影响JIT编译的所有因素。这就是为什么推荐的对代码进行基准测试的方法是使用特殊框架,其中大部分问题已经解决。我个人建议JMH

答案 2 :(得分:0)

当优化代码时,它会使用有关代码运行方式的信息进行优化。在test1()中,第一个循环触发整个方法进行优化,但是没有关于如何运行第二个循环的信息,因此它没有被优化,以及test2()

我期望应该发生的是该方法被重新优化,但是代码必须检测到它第一次做出的假设是无效的。

猜测可能有什么不同,test2()可能已经循环展开,而在test1()中则没有。这可以解释性能上的差异。