Java: 为什么第一次调用方法比较慢?

Java: Why is calling a method for the first time slower?

最近,我正在使用 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)。

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


更新:

我试着学习了一些JMH,写了一个测试程序

带有示例输出的代码:Code

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


为什么会这样?

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

更糟糕的是,与其他平台相比,您在 windows where nanotime performs worse 上这样做。


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

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

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


关键是您的测量值不能真正按照表面价值进行测量。即使第一次调用一个方法有一定的成本,你测量的时间也只是为此提供了一个上限。

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

有关类似问题,请参阅 this thread

请记住,这种行为通常取决于 environment/OS 它 运行 的状态。