最近,我正在使用Java编写插件,发现第一次从get()
检索元素(使用HashMap
)非常慢。最初,我想问一个问题并找到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
我运行了几次代码,结果大致相同。第一次调用在我的计算机上会更长(> 8000 ns)(Windows 8.1,Oracle Java 8u25)。
显然,第一次调用通常比以下调用慢(某些调用在随机情况下可能会更长)。
更新
我尝试学习一些JMH,并编写测试程序
代码w /样本输出:Code
我不知道它是否是一个合适的基准(如果程序有一些问题,告诉我),但我发现第一次热身迭代花费更多时间(我使用两次热身迭代以防万一特效影响结果)。而且我认为第一次热身应该是第一次打电话并且速度较慢。如果测试合适,就会出现这种现象。
那么为什么会这样呢?
答案 0 :(得分:0)
你在循环中调用System.nanoTime()
。这些调用不是免费的,所以除了空方法所花费的时间之外,你实际上是在测量退出nanime call#1并进入nanime call#2所需的时间。
更糟糕的是,与其他平台相比,你在Windows where nanotime performs worse上这样做了。
关于JMH:在这种情况下,我认为这没有多大帮助。它的设计目的是通过平均多次迭代来测量,以避免死代码消除,考虑JIT预热,避免排序依赖,...而且afaik它也只是在引擎盖下使用纳米级。
它的设计目标几乎与你想要衡量的目标相反。
您正在衡量某事。但是有些东西可能是几次缓存未命中,纳米级调用开销,一些JVM内部(类加载?解释器中的某种懒惰初始化?),......可能是它们的组合。
关键是你的测量不能真正按照面值进行。即使第一次调用方法有一定的成本,你测量的时间也只是提供了一个上限。
答案 1 :(得分:0)
这种行为通常是由编译器或RE引起的。它在第一次迭代后开始优化执行。另外类加载会产生影响(我猜你的示例代码不是这种情况,因为所有类都是在最新的第一个循环中加载的。)
有关类似问题,请参阅this thread。
请记住,这种行为通常取决于它所运行的环境/操作系统。