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 它 运行 的状态。
最近,我正在使用 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 它 运行 的状态。