调试(i!= t ++)vs(t ++!= i)

时间:2017-03-27 18:22:25

标签: java debugging

我正在解决一些必须使用(i!= t ++)条件的问题。不幸的是,使用这个条件给了TLE。但是当我使用(t ++!= i)而不是成功提交时。我认为两个条件都与下面的程序相同。我的编译器上的程序输出是 5259 图3分别是第一和第二循环所花费的时间 我无法找到这两个条件之间的区别。当然有一些我找不到的错误

void test()
{
    long b=System.currentTimeMillis();
    for(int i=0;i<100001;i++) {
        int t=0;
        while (i!=t++)
        {
            int x=34;
            x++;

        }
    }
    System.out.println(System.currentTimeMillis()-b);

    b=System.currentTimeMillis();
    for(int i=0;i<100001;i++) {
        int t=0;
        while (t++!=i)
        {
            int x=34;
            x--;
            x++;
        }
    }
}

3 个答案:

答案 0 :(得分:2)

这是一个部分答案,未来的调查是必要的。但是,目前答案是 - 这是JIT优化的影响。另请注意,微基准测试不是性能测试的最佳选择,尤其是对于像Java这样的动态编译语言(例如,参见this guru paper)。

我正在使用Windows 10 Home,java -version打印:

  

java版“1.8.0_121”
  Java(TM)SE运行时环境(版本1.8.0_121-b13)
  Java HotSpot(TM)64位服务器VM(版本25.121-b13,混合模式)

我已按如下方式重新构建您的代码,并将x添加为外部计数器,以确保循环未被优化:

void test1() {
    int x = 0;
    long b = System.currentTimeMillis();
    for (int i = 0; i < 100_001; i++) {
        int t = 0;
        while (i != t++) {
            x++;
        }
    }
    long b1 = System.currentTimeMillis();
    System.out.println("T(test1) = " + (b1 - b));
    System.out.println("x(test1) = " + x);
}

void test2() {
    int x=0;
    long b = System.currentTimeMillis();
    for (int i = 0; i < 100001; i++) {
        int t = 0;
        while (t++ != i) {
            x++;
        }
    }
    long b1 = System.currentTimeMillis();
    System.out.println("T(test2) = " + (b1 - b));
    System.out.println("x(test2) = " + x);
}

每个函数都被调用两次:

t.test1();
t.test1();
t.test2();
t.test2();

好的,让我们看一下标准java Test调用的结果(没有提供其他的互操作参数):

  

T(test1)= 3745
  x(test1)= 705082704
  T(test1)= 0
  x(test1)= 705082704
  T(test2)= 5
  x(test2)= 705082704
  T(test2)= 0
  x(test2)= 705082704

如您所见,在第二次调用之后,两种情况下的运行时间都等于0。即使我们将int x = 0初始化更改为int x = new Random().nextInt()以确保第二次调用结果未缓存或其他内容,也会发生相同的情况。通常,在进行测量之前应该“预热”Java解释器,即在同一次运行中测量代码性能两次并丢弃第一个结果,以便确保优化到位。但这是解决在线评委练习时没有的奢侈品。

现在换另一部分。 Oracle的JDK有一个-Xint解释器开关,可以完全关闭JIT。让我们使用它,看看会发生什么。我也使用了-XX:+PrintCompilation标志来确保没有任何编译正在发生(即解释器被调用为java -XX:+PrintCompilation -Xint Test;如果没有打印额外的诊断,这意味着代码没有被编译):

  

T(test1)= 56610
  x(test1)= 705082704
  T(test1)= 55635
  x(test1)= 705082704
  T(test2)= 60247
  x(test2)= 705082704
  T(test2)= 58249
  x(test2)= 705082704

两个观察结果:现在任务需要很长时间,并且所有调用的结果都相似。必须进行更多调查,以发现JIT对这两个代码进行不同优化的原因。

编辑:与JIT第2部分的乐趣

所以,我继续尝试不同的编译选项。一般来说,JIT使用两种类型的编译器。 C1(客户端)编译器旨在提供更快的JVM启动时间,但不如C2(服务器)编译器快。我使用的64位Java 8 JVM似乎使服务器成为唯一容易使用的选项(参见this FAQ;但是仍可以使用-XX:TieredStopAtLevel=标志选择不同的编译级别;为简洁起见,我不会粘贴我使用它的结果,但他们支持这样的论点,即服务器编译器版本使test2的第一次调用更快。

我的机器上也碰巧有32位JRE。它不支持服务器编译器并提供以下版本信息:

  

java版“1.8.0_121”
  Java(TM)SE运行时环境(版本1.8.0_121-b13)
  Java HotSpot(TM)客户端VM(版本25.121-b13,混合模式)

此JVM的结果如下:

  

T(test1)= 3947
  x(test1)= 705082704
  T(test1)= 3985
  x(test1)= 705082704
  T(test2)= 4031
  x(test2)= 705082704
  T(test2)= 4172
  x(test2)= 705082704

答案 1 :(得分:0)

所以我测试了你的代码并检查了它的运行时间。以下结果(循环1和2的毫秒时间):

 time1   time2 
 175.2   171.0
 189.6   187.1
 162.1   164.2
 162.3   162.1
 162.3   166.0

两个循环同时运行。

以下是代码

private static void test()
  {
    long b;

    float time1=0;
    float time2=0;

    int x=0;
    for(int l=0;l<10;l++) {
        b=System.currentTimeMillis();
        for(int i=0;i<20001;i++) {
          int t=0;
          while (i!=t++)
            {
            x++;
            }
        }


        time1+=System.currentTimeMillis()-b;

        b=System.currentTimeMillis();
        x=0;
        for(int i=0;i<20001;i++) 
            {
            int t=0;
            while (t++!=i) 
                {
                    x++;
                }


        }

    time2+=System.currentTimeMillis()-b;

    }

    time1/=10;
    time2/=10 ;   
    System.out.println(time1);  
    System.out.println(time2);
}

我不确定为什么你认为这些陈述会使情况发生。但我认为你的程序在你的在线编译器上耗尽了时间(有一个结束时间)。之后你改变了论点并认为它现在有效。 这可能是因为您的代码已经预热,可能会减少程序的符文时间。但这只是猜测....而且不应该作为答案。

请自己测试上面的代码。

回答你的问题,两个逻辑上的印象都是一样的。虽然后修复i ++(复制值,增加当前值,产生副本)和前缀++ i(增加当前值并产生结果)略有不同。第二个确实少了一个操作。但这不应该导致不同的计算时间。

查看这篇文章Why is "while (i++ < n) {}" significantly slower than "while (++i < n) {}",了解为什么IDE会给出一些奇怪的结果。

答案 2 :(得分:-1)

我最好的猜测是,它是如何生成和/或执行字节码的一个怪癖。

计算机处理器可以使用有限数量的寄存器来存储它们在任何给定时间处理的值。根据首先出现的变量,变量可能被放置在不同的寄存器中。如果变量在不同的寄存器中开始,那么可能会影响哪一个被移除以为其他东西腾出空间。

如果稍后需要某个值不在寄存器中,则处理器必须从计算机的存储器(或处理器的高速缓存)中获取它,这比花时间更长。它已经在登记册中了。

还有一些事情,处理器可以尝试在另一个语句完成之前开始执行一个语句。但是如果值发生变化,这可能会中断。如果t++在尝试再次加载t之前很快发生,可能是处理器被中断并且必须从头开始执行指令。 (但是,我不认为这是发生什么事情的主要原因;它不会产生如此大的影响。)

也许只是一些优化问题,Java编译器认为它可以在一个实例中完成,而不是另一个实例。