【问题标题】:Java: Why is calling a method for the first time slower?Java:为什么第一次调用方法会变慢?
【发布时间】:2015-07-09 10:02:47
【问题描述】:

最近,我正在使用 Java 编写一个插件,发现第一次从 HashMap 检索元素(使用 get())非常慢。最初,我想问一个问题并找到this(虽然没有答案)。然而,通过进一步的实验,我注意到这种现象发生在ArrayList 上,然后是所有方法。

代码如下:

public class Test {
    public static void main(String[] args) {
        long startTime, stopTime;

        // Method 1
        System.out.println("Test 1:");
        for (int i = 0; i < 20; ++i) {
            startTime = System.nanoTime();
            testMethod1();
            stopTime = System.nanoTime();
            System.out.println((stopTime - startTime) + "ns");
        }

        // Method 2
        System.out.println("Test 2:");
        for (int i = 0; i < 20; ++i) {
            startTime = System.nanoTime();
            testMethod2();
            stopTime = System.nanoTime();
            System.out.println((stopTime - startTime) + "ns");
        }
    }

    public static void testMethod1() {
        // Do nothing
    }

    public static void testMethod2() {
        // Do nothing
    }
}

片段:Test Snippet

输出会是这样的:

Test 1:
2485ns
505ns
453ns
603ns
362ns
414ns
424ns
488ns
325ns
426ns
618ns
794ns
389ns
686ns
464ns
375ns
354ns
442ns
404ns
450ns
Test 2:
3248ns
700ns
538ns
531ns
351ns
444ns
321ns
424ns
523ns
488ns
487ns
491ns
551ns
497ns
480ns
465ns
477ns
453ns
727ns
504ns

我运行了几次代码,结果大致相同。在我的计算机(Windows 8.1、Oracle Java 8u25)上,第一次调用会更长(>8000 ns)。

显然,第一次调用通常比后面的调用慢(某些调用在随机情况下可能会更长)。


更新:

我尝试学习了一些JMH,并编写了一个测试程序

带有示例输出的代码:Code

我不知道它是否是一个合适的基准(如果程序有问题,请告诉我),但我发现第一次热身迭代花费更多时间(我使用两次热身迭代以防-ups 影响结果)。而且我认为第一次热身应该是第一次调用并且速度较慢。所以这个现象是存在的,如果测试得当的话。


那为什么会这样呢?

【问题讨论】:

  • JIT编译你了解多少?
  • 至于问题的System.gc()部分,System.gc()不等待GC运行,它在设置了GC应该在最近的机会运行的标志后立即返回,这恰好在您的方法运行时。当然,收集的东西很少,在多核系统上,GC 可以与您的代码并行运行,但是在每个 GC 算法中都有停止世界的步骤。额外的延迟很可能来自于此。
  • 实用吗[...] - 问问自己:如果只调用一次,多花几纳秒重要吗?
  • 抱歉,我对此了解不多。是字节码吗?
  • @the8472 它在我的程序中将延迟夸大到大约 1 秒。

标签: java performance performance-testing


【解决方案1】:

您在循环中调用System.nanoTime()。这些调用不是免费的,所以除了空方法所用的时间之外,您实际上是在测量从 nanotime 调用 #1 退出和进入 nanotime call #2 所用的时间。

更糟糕的是,与其他平台相比,您在 Windows where nanotime performs worse 上执行此操作。


关于 JMH:我认为在这种情况下没有多大帮助。它旨在通过平均多次迭代来测量,以避免死代码消除,考虑 JIT 预热,避免排序依赖,......而且它也只是在引擎盖下使用纳米时间。

它的设计目标几乎与您要测量的目标相反。

您正在测量某物。但这可能是几个缓存未命中、纳米时间调用开销、一些 JVM 内部(类加载?解释器中的某种延迟初始化?),......可能是它们的组合。


关键是您的测量值不能真正按面值进行。即使第一次调用方法会产生一定的成本,但您测量的时间也只是提供了一个上限。

【讨论】:

  • 我确实在我的服务器(CentOS)上运行了测试,结果是一样的。
  • 我尝试了 JMH 并更新了问题。如果JMH程序有问题,请告诉我
【解决方案2】:

这种行为通常是由编译器或 RE 引起的。它在第一次迭代后开始优化执行。此外,类加载可能会产生影响(我想您的示例代码中并非如此,因为所有类都在最新的第一个循环中加载)。

请参阅this thread 了解类似问题。

请记住,这种行为通常取决于其运行的环境/操作系统。

【讨论】:

  • 第一次迭代后 JIT 不启动。通常,一个方法必须在编译器启动之前已经执行了数百次。尝试在禁用 JIT 的情况下运行测试用例 (-Xint),您将观察到相同的结果。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-05-12
  • 2017-05-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
相关资源
最近更新 更多