这只是一个初步的答案,未来还需要更多调查。但是目前的答案是——
这是JIT优化的效果。此外,需要注意的是微基准测试不是性能测试的最佳选择,特别是在像Java这样的动态编译语言中(例如,请参见
这篇专家论文)。
我正在使用Windows 10 Home,
java -version
命令输出如下:
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
我已经对您的代码进行了重构,并添加了
x
作为外部计数器来确保循环不会被优化掉。
void test1() {
int x = 0;
long b = System.currentTimeMillis();
for (int i = 0; i < 100_001; i++) {
int t = 0;
while (i != t++) {
x++;
}
}
long b1 = System.currentTimeMillis();
System.out.println("T(test1) = " + (b1 - b));
System.out.println("x(test1) = " + x);
}
void test2() {
int x=0;
long b = System.currentTimeMillis();
for (int i = 0; i < 100001; i++) {
int t = 0;
while (t++ != i) {
x++;
}
}
long b1 = System.currentTimeMillis();
System.out.println("T(test2) = " + (b1 - b));
System.out.println("x(test2) = " + x);
}
每个函数被调用两次:
t.test1();
t.test1();
t.test2();
t.test2();
好的,让我们看一下标准
java Test
调用的结果(没有提供其他解释器参数):
T(test1) = 3745
x(test1) = 705082704
T(test1) = 0
x(test1) = 705082704
T(test2) = 5
x(test2) = 705082704
T(test2) = 0
x(test2) = 705082704
如您所见,在第二次调用之后,两种情况下的运行时间都等于0。即使我们将
int x = 0
的初始化更改为
int x = new Random().nextInt()
以确保第二次调用的结果没有被缓存或其他什么,情况也是如此。通常,应在进行测量之前“热身”Java解释器,即在同一次运行中测量代码性能两次,并且舍弃第一个结果,以确保优化已经就位。但这是在线评测习题时不可得到的奢侈品。
现在来看另一部分。Oracle的JDK有一个
-Xint
解释器开关,可以完全关闭JIT。让我们使用它并看看会发生什么。我还使用了
-XX:+PrintCompilation
标志,以确保不进行任何编译(即解释器被调用为
java -XX:+PrintCompilation -Xint Test
;如果没有打印其他诊断信息,则表示代码未编译):
T(test1) = 56610
x(test1) = 705082704
T(test1) = 55635
x(test1) = 705082704
T(test2) = 60247
x(test2) = 705082704
T(test2) = 58249
x(test2) = 705082704
两个观察结果:现在任务需要很长时间,并且所有调用的结果都相似。必须进行更多的调查,以发现为什么JIT会对这两个代码进行不同的优化。
编辑:
有趣的JIT第二部分
所以,我开始尝试不同的编译选项。一般来说,JIT 使用两种类型的编译器。C1 (客户端) 编译器旨在提供更快的 JVM 启动时间,但不如 C2 (服务器端) 编译器快。我使用的 64 位 Java 8 JVM 似乎只有服务器是唯一可用的选项(请参见
此 FAQ;但是可以使用
-XX:TieredStopAtLevel=
标志选择不同的编译级别;为了简洁,我不会粘贴使用它获取的结果,但它们支持这个命题,即第一个调用
test2
的速度更快)。
我也恰好在我的机器上安装了 32 位 JRE。它不支持服务器编译器,并给出以下版本信息:
java 版本 "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) Client VM (build 25.121-b13, mixed mode)
这个 JVM 的结果如下所示:
T(test1) = 3947
x(test1) = 705082704
T(test1) = 3985
x(test1) = 705082704
T(test2) = 4031
x(test2) = 705082704
T(test2) = 4172
x(test2) = 705082704
while (i!=t++)
也会更慢。此外,我通过添加计数器验证了两个循环运行相同的次数。 - D M