【问题标题】:debugging (i!=t++) vs ( t++!=i)调试 (i!=t++) 与 (t++!=i)
【发布时间】:2017-08-20 14:17:51
【问题描述】:

我正在解决一些必须使用 (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++;
        }
    }
}

【问题讨论】:

  • TLE 代表什么?
  • 时间限制超过
  • @Omore 这不是问题。 t++ 用于两个地方。
  • 我已经复制了这些结果,并且还验证了这不仅仅是因为第一个循环首先运行 - 即使我将 while (i!=t++) 放在另一个循环之后,它也会更慢。此外,我通过添加计数器验证了两个循环运行的次数相同。

标签: java debugging


【解决方案1】:

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

我正在使用 Windows 10 家庭版,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) 客户端虚拟机(build 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

【讨论】:

    【解决方案2】:

    所以我已经测试了你的代码并检查了它运行了多长时间。结果如下(循环 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 会给出一些奇怪的结果。

    【讨论】:

    • 我运行了你的代码,得到了 193.8 和 0.5 的时间。你有什么 Java 版本?
    【解决方案3】:

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

    计算机处理器的寄存器数量有限,它们可以用来存储它们在任何给定时间处理的值。变量可能被放置在不同的寄存器中,具体取决于哪个寄存器首先出现。如果变量在开始时位于不同的寄存器中,这可能会影响删除哪个变量以便为其他内容腾出空间。

    如果一个值在以后需要时不在寄存器中,处理器将不得不从计算机的内存(或处理器的缓存)中获取它,这比它已经在寄存器中要花费更多的时间。

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

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

    【讨论】:

    • 它认为这不是原因。如果我们为每个循环创建 2 个不同的程序会怎样。然后执行时间也给出了很大的差异。我没有明白你在第 4 段 t 和 t++ 中想说什么。我的意思是两个循环都有 t++ 。所以你想说,问题是由于并行处理
    猜你喜欢
    • 2011-09-01
    • 1970-01-01
    • 2015-03-21
    • 2015-08-26
    • 1970-01-01
    • 2017-06-11
    • 2022-12-27
    • 2020-07-13
    • 1970-01-01
    相关资源
    最近更新 更多