Java循环优化
给出以下(直接)代码: 你能给我解释一下大的时差吗?理论上,循环实现了相同的功能,但实际上,四个版本中的每一个似乎都有相应的时间差 重复执行后,结果几乎相同 以后编辑 作为另一个测试,我重写了主要方法:Java循环优化,java,performance,optimization,Java,Performance,Optimization,给出以下(直接)代码: 你能给我解释一下大的时差吗?理论上,循环实现了相同的功能,但实际上,四个版本中的每一个似乎都有相应的时间差 重复执行后,结果几乎相同 以后编辑 作为另一个测试,我重写了主要方法: public static void main(String args[]){ for(int i = 0; i < 4; ++i){ f1(); f2(); f3(); f4(); } } 对于10次重复: f1(): 5844 f2(): 8156 f
public static void main(String args[]){
for(int i = 0; i < 4; ++i){
f1(); f2(); f3(); f4();
}
}
对于10次重复:
f1(): 5844
f2(): 8156
f3(): 3453
f4(): 3813
f1(): 5844
f2(): 8218
f3(): 3485
f4(): 3937
f1(): 5985
f2(): 8156
f3(): 3422
f4(): 3781
f1(): 5828
f2(): 8234
f3(): 3469
f4(): 3828
f1(): 5844
f2(): 8328
f3(): 3422
f4(): 3859
f1(): 5844
f2(): 8188
f3(): 3406
f4(): 3797
f1(): 5906
f2(): 8219
f3(): 3422
f4(): 3797
f1(): 5843
f2(): 8203
f3(): 3454
f4(): 3906
f1(): 5844
f2(): 8140
f3(): 3469
f4(): 3812
f1(): 5860
f2(): 8109
f3(): 3422
f4(): 3813
除去循环之间的演算后,结果仍然有点不同:
public class pr2 {
public static void f1(){
long sx = 0, s;
s = System.currentTimeMillis();
for(long i = 0; i < Integer.MAX_VALUE; ++i);
System.out.println("f1(): " + (System.currentTimeMillis() - s));
}
public static void f2(){
long sx = 0, s, i;
s = System.currentTimeMillis();
i = Integer.MAX_VALUE;
while(i-->0);
System.out.println("f2(): " + (System.currentTimeMillis() - s));
}
public static void f3(){
long sx = 0, s, i;
s = System.currentTimeMillis();
i = Integer.MAX_VALUE;
while(--i>0);
System.out.println("f3(): " + (System.currentTimeMillis() - s));
}
public static void f4(){
long sx = 0, s, i;
s = System.currentTimeMillis();
i = Integer.MAX_VALUE;
do{
}while(--i>0);
System.out.println("f4(): " + (System.currentTimeMillis() - s));
}
public static void main(String args[]){
for(int i = 0; i < 2; ++i){
f1(); f2(); f3(); f4();
}
}
}
JVM:
以后编辑:
对于第一个版本,我为javac使用了-O参数。新的结果是:
f1(): 5906
f2(): 8266
f3(): 3406
f4(): 3844
f1(): 5843
f2(): 8125
f3(): 3438
f4(): 3859
f1(): 5891
f2(): 8156
f3(): 3406
f4(): 3813
f1(): 5859
f2(): 8172
f3(): 3438
f4(): 3828
f1(): 3219
f2(): 4859
f3(): 2610
f4(): 3031
以后编辑
好的,我在家里也试过同样的代码,使用Linux机器:
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8) (6b18-1.8-0ubuntu1)
OpenJDK Server VM (build 14.0-b16, mixed mode)
结果是“正常的”。现在没有问题了:
f1(): 7495
f2(): 7418
f3(): 7457
f4(): 7384
实际上,您是在对JVM进行基准测试,而不是对代码进行基准测试 另见:
更新:好的,这是一个简短的回答。使用后缀运算符(
i--
)的循环似乎比使用前缀运算符(--i
)的循环慢。这可能是真的,因为值在表达式求值期间发生了更改,但编译器需要保留原始值的副本才能在表达式中使用。使用prefix运算符可以避免保存副本,因为表达式中将只使用更改后的值
另见:
- JVM的垃圾收集可能会失败 在一些死刑执行过程中被执行
- 您的操作系统计划其他 要执行的任务,因此也为其他应用程序提供了资源。甚至优先考虑他们 由于编译do/while和for的方式,可能会有一些差异,但这些应该忽略不计
这一定是由于操作系统或Java为windows编译的方式,我已经在windows上对其进行了测试,并在windows上获得了与您相同的结果。在多次运行后,Hotspot编译器可能会优化每个方法。这需要时间和变化。但由于循环的工作方式大致相同,因此时间最终会变得相似似乎是合理的。当我在JVM(Java HotSpot(TM)64位服务器VM(build 16.0-b13,混合模式))上运行此代码时,所有四个函数都给出了类似的结果:
f1(): 3234
f2(): 3132
f3(): 3114
f4(): 3089
我猜您的JVM在某些地方没有进行相同的优化
您可以使用javap:javap-l-cpr1
检查为不同函数生成的字节码。当我这样做时,f2()会得到以下结果:
publicstaticvoidf2();
代码:
0:lconst_0
1:lstore_0
2:invokestatic#2//方法java/lang/System.currentTimeMillis:()J
5:lstore_2
6:ldc2#U w#3//长2147483647l
9:1商店4
11:lload 4
13:2
14:lconst_1
15:lsub
16:1商店4
18:lconst_0
19:lcmp
20:ifle 31
23:lload_0
24:lload 4
26:ladd
27:lstore_0
28:goto 11
31:lload_0
32:ldc2#w#3//长2147483647l
35:ladd
36:lstore_0
37:getstatic#5//字段java/lang/System.out:Ljava/io/PrintStream;
40:新的#6//类java/lang/StringBuilder
43:dup
44:特别是#7//方法java/lang/StringBuilder。“”:()V
47:最不发达国家13//字符串f2():
49:invokevirtual#9//方法java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
52:invokestatic#2//方法java/lang/System.currentTimeMillis:()J
55:lload_2
56:lsub
57:invokevirtual#10//方法java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
60:invokevirtual#11//方法java/lang/StringBuilder.toString:()Ljava/lang/String;
63:invokevirtual#12//方法java/io/PrintStream.println:(Ljava/lang/String;)V
66:返回
f2()速度较慢的一个可能原因可能是编译器/JVM在
while(i-->0)
后减量运算符方面不够聪明。基本上,在增量之前和之后都需要i
的值,因此如果简单地执行此操作,则会涉及更多的工作。我怀疑这与在具有32位ALU的机器上执行64位算术有关。我怀疑在递增/递减之前/之后的某些测试组合在本机指令级别需要更长的时间,这是由于微妙的流水线效应。有人报告说64位机器上的数字是持平的,这一事实支持了这一理论。确认这一点的方法是获取JIT编译器生成的本机代码的转储,获取特定CPU的文档,并找出时钟周期的去向
但老实说,我不知道这是否值得。我们有明确的证据表明,您的微基准测试数据依赖于CPU,并且所做的“工作”显然不具有代表性。(为什么要在32位机器上使用长循环计数器?)
我也有点惊讶,JIT编译器没有发现循环(在每种情况下)可以完全优化。这是可复制的还是一次性的?java的规则是:JVM比你聪明,不要尝试和超越它it@Andreas_D可复制的你自己试试代码。@Andrei-如果我手头有编译器,我会的;)-请检查BalusC的第三个列表项,然后再试一次好吗?知道吗,看起来后减量比前减量慢得多,但是-正如BalusC所显示的,它可能是一个“jvm优化工件”
f(3)
的运行时间比f(2)
少一倍,因为在比较之前会减量。@InsertNickHere请检查链接I
f1(): 3219
f2(): 4859
f3(): 2610
f4(): 3031
java version "1.6.0_18"
OpenJDK Runtime Environment (IcedTea6 1.8) (6b18-1.8-0ubuntu1)
OpenJDK Server VM (build 14.0-b16, mixed mode)
f1(): 7495
f2(): 7418
f3(): 7457
f4(): 7384
f1(): 3234
f2(): 3132
f3(): 3114
f4(): 3089
public static void f2();
Code:
0: lconst_0
1: lstore_0
2: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
5: lstore_2
6: ldc2_w #3; //long 2147483647l
9: lstore 4
11: lload 4
13: dup2
14: lconst_1
15: lsub
16: lstore 4
18: lconst_0
19: lcmp
20: ifle 31
23: lload_0
24: lload 4
26: ladd
27: lstore_0
28: goto 11
31: lload_0
32: ldc2_w #3; //long 2147483647l
35: ladd
36: lstore_0
37: getstatic #5; //Field java/lang/System.out:Ljava/io/PrintStream;
40: new #6; //class java/lang/StringBuilder
43: dup
44: invokespecial #7; //Method java/lang/StringBuilder."<init>":()V
47: ldc #13; //String f2():
49: invokevirtual #9; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
52: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
55: lload_2
56: lsub
57: invokevirtual #10; //Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
60: invokevirtual #11; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: return