第一次运行Java循环为什么会很慢?[Sun HotSpot 1.5,sparc]

11

在对 Solaris SPARC 上的一些 Java 代码进行基准测试时,我注意到第一次调用基准测试的函数速度非常慢(差了 10 倍):

  • 第一次 | 1 | 25295.979 毫秒
  • 第二次 | 1 | 2256.990 毫秒
  • 第三次 | 1 | 2250.575 毫秒

为什么会这样?我怀疑是 JIT 编译器,有没有办法验证这一点?

编辑: 根据一些答案,我想澄清一下,这段代码是我能找到的展示此行为的最简单的测试用例。所以我的目标不是让它跑得更快,而是要理解发生了什么,以便我在我的实际基准测试中避免这种情况。

已解决:Tom Hawtin 正确指出了我的“慢”时间实际上是合理的。在这个观察之后,我附加了一个调试器到 Java 进程中。在第一次运行中,内部循环如下所示:

0xf9037218:     cmp      %l0, 100
0xf903721c:     bge,pn   %icc,0xf90371f4        ! 0xf90371f4
0xf9037220:     nop
0xf9037224:     ld       [%l3 + 92], %l2
0xf9037228:     ld       [%l2 + 8], %l6
0xf903722c:     add      %l6, 1, %l5
0xf9037230:     st       %l5, [%l2 + 8]
0xf9037234:     inc      %l0
0xf9037238:     ld       [%l1], %g0
0xf903723c:     ba,pt    %icc,0xf9037218        ! 0xf9037218

在接下来的迭代中,循环看起来像这样:

0xf90377d4:     sub      %l2, %l0, %l3
0xf90377d8:     add      %l3, %l0, %l2
0xf90377dc:     add      %l2, 1, %l4
0xf90377e0:     inc      %l0
0xf90377e4:     cmp      %l0, 100
0xf90377e8:     bl,pn    %icc,0xf90377d8        ! 0xf90377d8

因此,HotSpot从内部循环中删除了内存访问,使其加速了一个数量级。

教训:要做好数学计算!我应该自己做汤姆的计算。

基准测试Java代码:

    private int counter;
    private int nThreads;

    private void measure(String tag) throws Exception {
            MyThread threads[] = new MyThread[nThreads];
            int i;

            counter = 0;

            for (i = 0; i < nThreads; i++)
                    threads[i] = new MyThread();

            long start = System.nanoTime();

            for (i = 0; i < nThreads; i++)
                    threads[i].start();

            for (i = 0; i < nThreads; i++)
                    threads[i].join();

            if (tag != null)
                    System.out.format("%-20s | %-2d | %.3f ms \n", tag, nThreads,
                                     new Double((System.nanoTime() - start) / 1000000.0));
    }
    public MyBench() {
            try {
                    this.nThreads = 1;
                    measure("First");
                    measure("Second");
                    measure("Third");
            } catch (Exception e) {
                    System.out.println("Error: " + e);
            }
    }

    private class MyThread extends Thread {
            public void run() {
                    while (counter < 10000000) {
                            // work
                            for (int j = 0; j < 100; j++)
                                    counter++;
                            counter -= 99;
                    }
            }
    }

我想不到一种简单的方法来验证它是 JIT 编译器,但我可以说几乎肯定是 Hotspot 开始将循环编译成本地代码。 - Kekoa
1
顺便说一下,如果您有多个线程,这个基准测试将会更加不可靠。因为您正在使用全局变量来控制外部循环,每个线程很可能循环的次数不同;可能多于10000000次,也可能少于。 - Michael Myers
1
顺便问一下(再次),这可能是一个愚蠢的问题,但你是否运行过类似结果的多次测试?单个测试基本上没有意义。 - Michael Myers
根据我的计算,在1 GHz处理器上,即使是“快速”时间每个增量也需要200个周期,这对我来说似乎太慢了(我不会惊讶地看到-server将所有优化都消除)。 - Tom Hawtin - tackline
啊。我的错误,我读错了代码... - Tom Hawtin - tackline
11个回答

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接