第一次Java循环运行缓慢,为什么?[太阳热点1.5,sparc]
在Solaris SPARC机器上对一些Java代码进行基准测试时,我注意到,第一次调用基准测试函数时,它的运行速度非常慢(相差10倍):第一次Java循环运行缓慢,为什么?[太阳热点1.5,sparc],java,performance,jvm-hotspot,microbenchmark,Java,Performance,Jvm Hotspot,Microbenchmark,在Solaris SPARC机器上对一些Java代码进行基准测试时,我注意到,第一次调用基准测试函数时,它的运行速度非常慢(相差10倍): 第一个| 1 | 25295.979毫秒 第二个| 1 | 2256.990毫秒 第三个| 1 | 2250.575毫秒 为什么会这样?我怀疑是JIT编译器,有没有办法验证这一点 编辑:根据一些答案,我想澄清这段代码是最简单的 可能的测试案例,我可以找到展示这种行为。所以我的目标不是 它需要跑得快,但要了解发生了什么,这样我才能在真实的生活中避免它 基准
- 第一个| 1 | 25295.979毫秒
- 第二个| 1 | 2256.990毫秒
- 第三个| 1 | 2250.575毫秒
0xf9037218: cmp %l0, 100
0xf903721c: bge,pn %icc,0xf90371f4 ! 0xf90371f4
0xf9037220: nop
0xf9037224: ld [%l3 + 92], %l2
0xf9037228: ld [%l2 + 8], %l6
0xf903722c: add %l6, 1, %l5
0xf9037230: st %l5, [%l2 + 8]
0xf9037234: inc %l0
0xf9037238: ld [%l1], %g0
0xf903723c: ba,pt %icc,0xf9037218 ! 0xf9037218
0xf90377d4: sub %l2, %l0, %l3
0xf90377d8: add %l3, %l0, %l2
0xf90377dc: add %l2, 1, %l4
0xf90377e0: inc %l0
0xf90377e4: cmp %l0, 100
0xf90377e8: bl,pn %icc,0xf90377d8 ! 0xf90377d8
public MyBench() {
try {
this.nThreads = 1;
new Mark1();
measure("First");
new Mark2();
measure("Second");
new Mark3();
measure("Third");
new Mark4();
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
private static class Mark1 {
}
private static class Mark2 {
}
private static class Mark3 {
}
private static class Mark4 {
}
在以下迭代中,循环如下所示:
0xf9037218: cmp %l0, 100
0xf903721c: bge,pn %icc,0xf90371f4 ! 0xf90371f4
0xf9037220: nop
0xf9037224: ld [%l3 + 92], %l2
0xf9037228: ld [%l2 + 8], %l6
0xf903722c: add %l6, 1, %l5
0xf9037230: st %l5, [%l2 + 8]
0xf9037234: inc %l0
0xf9037238: ld [%l1], %g0
0xf903723c: ba,pt %icc,0xf9037218 ! 0xf9037218
0xf90377d4: sub %l2, %l0, %l3
0xf90377d8: add %l3, %l0, %l2
0xf90377dc: add %l2, 1, %l4
0xf90377e0: inc %l0
0xf90377e4: cmp %l0, 100
0xf90377e8: bl,pn %icc,0xf90377d8 ! 0xf90377d8
public MyBench() {
try {
this.nThreads = 1;
new Mark1();
measure("First");
new Mark2();
measure("Second");
new Mark3();
measure("Third");
new Mark4();
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
private static class Mark1 {
}
private static class Mark2 {
}
private static class Mark3 {
}
private static class Mark4 {
}
因此,HotSpot从内部循环中删除了内存访问,将其速度提高了一个数量级
课程:做数学题!我应该自己做汤姆的计算
基准Java代码:
private int counter;
private int nThreads;
private void measure(String tag) throws Exception {
MyThread threads[] = new MyThread[nThreads];
int i;
counter = 0;
for (i = 0; i < nThreads; i++)
threads[i] = new MyThread();
long start = System.nanoTime();
for (i = 0; i < nThreads; i++)
threads[i].start();
for (i = 0; i < nThreads; i++)
threads[i].join();
if (tag != null)
System.out.format("%-20s | %-2d | %.3f ms \n", tag, nThreads,
new Double((System.nanoTime() - start) / 1000000.0));
}
public MyBench() {
try {
this.nThreads = 1;
measure("First");
measure("Second");
measure("Third");
} catch (Exception e) {
System.out.println("Error: " + e);
}
}
private class MyThread extends Thread {
public void run() {
while (counter < 10000000) {
// work
for (int j = 0; j < 100; j++)
counter++;
counter -= 99;
}
}
}
专用整数计数器;
私人阅读;
私有void度量值(字符串标记)引发异常{
MyThread线程[]=新的MyThread线程[];
int i;
计数器=0;
对于(i=0;i
这是一个有趣的问题。我怀疑JIT编译器,但以下是我的数字:
First | 1 | 2399.233 ms
Second | 1 | 2322.359 ms
Third | 1 | 2408.342 ms
第一个| 1 | 2399.233毫秒
第二个| 1 | 2322.359毫秒
第三个| 1 | 2408.342毫秒
可能Solaris正在用线程做一些有趣的事情;你试过使用nThreads=10左右吗?这是工作中的热点编译器。AFAIK,第一次运行函数时,运行“解释”并分析执行路径,然后JIT编译器可以优化后续函数调用 将类加载添加为可疑对象。类在第一次引用时被延迟加载。因此,代码第一次运行时,您可能是第一次引用某些类。我建议您使用nThread=Runtime.getRuntime().availableProcessors(),这将为您提供使用系统中所有核心的最佳线程数
您可以尝试关闭JIT,看看它有什么不同。它肯定是热点编译器。如果您在64位solaris上运行,它默认为服务器VM和hotspot,只需在第一次执行时开始优化即可。在客户端VM上,在热点启动之前,代码可能需要运行几次。(我相信solaris只有服务器vm,但我可能错了)您可以让vm记录有关类加载和编译的信息,请尝试以下vm参数: -XX:+打印编译 -XX:+TraceClassLoading 这可能会为引擎盖下发生的事情提供进一步的线索 编辑:我不确定这些选项在Java1.5中是否有效(我在1.6中使用过它们)。我会试着检查一下。。。
再次编辑:它在java 1.5中工作(请注意,您需要+,而不是-,或者关闭选项…有关启动器如何在客户端和服务器VM之间进行选择的信息,请参见,以及不同处理器和操作系统上支持的功能。验证JIT编译器是否是后续迭代中加速的原因的最佳方法是在JIT编译器关闭的情况下运行基准测试。为此,请指定系统属性
java.compiler=NONE
(单词“NONE”必须为大写)
花费在类加载上的时间也会导致基准代码在第一次运行时运行变慢。最后,在调用Thread.start()和调用线程的run()方法之间存在不确定的延迟
<>你可能想考虑找到一个基准框架。一个好的框架将通过运行几个迭代来“预热”代码,然后使用不同的迭代次数进行多次计时。请参阅。我相信您也可以使用-Xint的java命令的非标准选项来禁用HotSpot,并仅对代码进行解释。这至少可以让HotSpot从解释时间的等式中消失。一些丑陋、不切实际的代码(微基准的东西):
while(计数器<10000000){
//工作
对于(int j=0;j<100;j++)
计数器++;
计数器-=99;
}
那么这是在做什么,它应该以多快的速度运行
内环使计数器递增100倍,然后计数器递减99倍。因此,增量为1。注意:计数器是外部类的成员变量,因此存在一些开销。然后运行10000000次。所以内心
[Loaded MyBench$Mark3 from file:/D:/DEVEL/Test/classes/]
Third | 1 | 2093,659 ms