为什么这个相同的代码会减速?

时间:2015-08-31 21:03:10

标签: java

我试图测量执行此循环的时间:

Get-Content

结果不一致。最终,我设法通过以下代码获得了一致的结果:

for (boolean t : test) {
    if (!t)
        ++count;
}

我得到的结果是:

public class Test {
    public static void main(String[] args) {
        int size = 100;
        boolean[] test = new boolean[10_000_000];
        java.util.Random r = new java.util.Random();
        for (int n = 0; n < 10_000_000; ++n)
            test[n] = !r.nextBoolean();

        int expected = 0;
        long acumulated = 0;
        for (int repeat = -1; repeat < size; ++repeat) {
            int count = 0;
            long start = System.currentTimeMillis();
            for (boolean t : test) {
                if (!t)
                    ++count;
            }
            long end = System.currentTimeMillis();
            if (repeat != -1)  // First run does not count, VM warming up
                acumulated += end - start;
            else                  // Use count to avoid compiler or JVM
                expected = count; //optimization of inner loop
            if ( count!=expected )
                throw new Error("Tests don't run same ammount of times");
        }
        float average = (float) acumulated / size;
        System.out.println("1st test : " + average);

        int expectedBis = 0;
        acumulated = 0;
        if ( "reassign".equals(args[0])) {
            for (int n = 0; n < 10_000_000; ++n)
                test[n] = test[n];
        }
        for (int repeat = -1; repeat < size; ++repeat) {
            int count = 0;
            long start = System.currentTimeMillis();
            for (boolean t : test) {
                if (!t)
                    ++count;
            }
            long end = System.currentTimeMillis();
            if (repeat != -1)  // First run does not count, VM warming up
                acumulated += end - start;
            else                     // Use count to avoid compiler or JVM
                expectedBis = count; //optimization of inner loop
            if ( count!=expected || count!=expectedBis)
                throw new Error("Tests don't run same ammount of times");
        }
        average = (float) acumulated / size;
        System.out.println("2nd test : " + average);
    }

}

区别在于在第二次测试之前执行或不执行此循环。

$ java -jar Test.jar noreassign
1st test : 23.98
2nd test : 23.97
$ java -jar Test.jar reassign
1st test : 23.98
2nd test : 40.86
$ java -version
java version "1.7.0_79"
OpenJDK Runtime Environment (IcedTea 2.5.5) (Gentoo package icedtea-7.2.5.5)
OpenJDK 64-Bit Server VM (build 24.79-b02, mixed mode)

为什么呢?为什么这样的重新分配导致它之后的那些循环花费两倍的时间? 正确分析是很难的......

3 个答案:

答案 0 :(得分:3)

我会将此添加为评论,但我的声誉太低,因此必须将其添加为答案。

我已经用您的确切代码创建了一个jar,并运行了几次。我还将代码复制到C#并在.NET运行时运行它。

Java和C#都显示相同的确切时间,无论是否重新分配&#39;环。

如果将循环更改为

,您将获得什么时间
 if ( "reassign".equals(args[0])) {
        for (int n = 0; n < 5_000_000; ++n)
            test[n] = test[n];
    }

答案 1 :(得分:3)

Marko Topolniks和rossum的评论让我朝着正确的方向前进。
这是一个JIT编译器问题 如果我禁用JIT编译器,我会得到以下结果:

$ java -jar Test.jar -Djava.compiler=NONE noreassign  
1st test : 19.23  
2nd test : 19.33  
$ java -jar Test.jar -Djava.compiler=NONE reassign  
1st test : 19.23  
2nd test : 19.32  

一旦JIT编译器被停用,奇怪的减速就会消失 至于JIT编译器导致这种行为的原因......这超出了我的技能和知识 但是,正如Marius Dornean的测试所显示的那样,并非在所有JVM中都会发生这种情况。

答案 2 :(得分:3)

  

“至于JIT编译器导致这种行为的原因......这超出了我的技能和知识。”

三个基本事实:

  1. JIT编译后代码运行得更快。

  2. 在一大块代码运行一段时间后触发JIT编译。 (“有点”多长时间会影响JVM平台和命令行选项。)

  3. JIT编译需要时间。

  4. 在您的情况下,当您在测试1和测试2之间插入大分配循环时,您很可能会移动触发JIT编译的时间点...从测试2到2个测试之间。

    在这种情况下解决这个问题的简单方法是将main的主体放入循环并重复运行。然后在前几次运行中丢弃异常结果。

    (关闭JIT编译不是一个好的答案。通常,JIT编译之后的代码的性能特征将指示真实应用程序的执行方式......)< / p>

    通过将编译器设置为NONE,您将禁用JIT编译,将其从等式中删除。

    当人们试图手工编写微基准时,这种异常很常见。阅读此Q&amp; A: