我正在比较Java中嵌套for,while和do-while循环的效率,而且我遇到了一些我需要帮助理解的奇怪结果。
public class Loops {
public static void main(String[] args) {
int L = 100000; // number of iterations per loop
// for loop
double start = System.currentTimeMillis();
long s1 = 0;
for (int i=0; i < L; i++) {
for (int j = 0; j < L; j++) {
s1 += 1;
}
}
double end = System.currentTimeMillis();
String result1 = String.format("for loop: %.5f", (end-start) / 1000);
System.out.println(s1);
System.out.println(result1);
// do-while loop
double start1 = System.currentTimeMillis();
int i = 0;
long s2 = 0;
do {
i++;
int j = 0;
do {
s2 += 1;
j++;
} while (j < L);
} while (i < L);
double end1 = System.currentTimeMillis();
String result2 = String.format("do-while: %.5f", (end1-start1) / 1000);
System.out.println(s2);
System.out.println(result2);
// while loop
double start2 = System.currentTimeMillis();
i = 0;
long s3 = 0;
while (i < L) {
i++;
int j = 0;
while (j < L) {
s3 += 1;
j++;
}
}
double end2 = System.currentTimeMillis();
String result3 = String.format("while: %.5f", (end2-start2) / 1000);
System.out.println(s3);
System.out.println(result3);
}
}
所有循环各自的计数器总和达到100亿;结果让我很困惑:
for loop:6.48300
do-while:0.41200
while:9.71500
为什么do-while循环要快得多?这个性能差距与L的任何变化并行缩放。我独立地运行这些循环并且它们执行相同的操作。
答案 0 :(得分:20)
我已经运行了您提供的代码,并且惊讶地发现这些性能差异。由于好奇,我开始调查并发现尽管这些循环似乎在做同样的事情,但它们之间存在一些重要的差异。
首次运行这些循环后的结果是:
for loop: 1.43100
do-while: 0.51300
while: 1.54500
但是当我运行这三个循环至少10次时,每个循环的性能几乎相同。
for loop: 0.43200
do-while: 0.46100
while: 0.42900
JIT能够随着时间的推移优化这些循环,但必须存在一些不同之处,导致这些循环具有不同的初始性能。事实上,实际上存在两个不同之处:
do-while
循环的比较次数少于for
和while
循环 为简单起见,假设L = 1
long s1 = 0;
for (int i=0; i < L; i++) {
for (int j = 0; j < L; j++) {
s1 += 1;
外环:0&lt; 1
内环:0&lt; 1
内环:1&lt; 1
外环:1&lt; 1个
总计4次比较
int i = 0;
long s2 = 0;
do {
i++;
int j = 0;
do {
s2 += 1;
j++;
} while (j < L);
} while (i < L);
内环:1&lt; 1
外环:1&lt; 1个
总计2次比较
为了进一步调查,我稍微改变了你的课程,没有影响它的工作方式。
public class Loops {
final static int L = 100000; // number of iterations per loop
public static void main(String[] args) {
int round = 10;
while (round-- > 0) {
forLoop();
doWhileLoop();
whileLoop();
}
}
private static long whileLoop() {
int i = 0;
long s3 = 0;
while (i++ < L) {
int j = 0;
while (j++ < L) {
s3 += 1;
}
}
return s3;
}
private static long doWhileLoop() {
int i = 0;
long s2 = 0;
do {
int j = 0;
do {
s2 += 1;
} while (++j < L);
} while (++i < L);
return s2;
}
private static long forLoop() {
long s1 = 0;
for (int i = 0; i < L; i++) {
for (int j = 0; j < L; j++) {
s1 += 1;
}
}
return s1;
}
}
然后编译它并调用javap -c -s -private -l Loop
来获取字节码。
首先是doWhileLoop的字节码。
0: iconst_0 // push the int value 0 onto the stack
1: istore_1 // store int value into variable 1 (i)
2: lconst_0 // push the long 0 onto the stack
3: lstore_2 // store a long value in a local variable 2 (s2)
4: iconst_0 // push the int value 0 onto the stack
5: istore 4 // store int value into variable 4 (j)
7: lload_2 // load a long value from a local variable 2 (i)
8: lconst_1 // push the long 1 onto the stack
9: ladd // add two longs
10: lstore_2 // store a long value in a local variable 2 (i)
11: iinc 4, 1 // increment local variable 4 (j) by signed byte 1
14: iload 4 // load an int value from a local variable 4 (j)
16: iload_0 // load an int value from a local variable 0 (L)
17: if_icmplt 7 // if value1 is less than value2, branch to instruction at 7
20: iinc 1, 1 // increment local variable 1 (i) by signed byte 1
23: iload_1 // load an int value from a local variable 1 (i)
24: iload_0 // load an int value from a local variable 0 (L)
25: if_icmplt 4 // if value1 is less than value2, branch to instruction at 4
28: lload_2 // load a long value from a local variable 2 (s2)
29: lreturn // return a long value
现在是whileLooP的字节码:
0: iconst_0 // push int value 0 onto the stack
1: istore_1 // store int value into variable 1 (i)
2: lconst_0 // push the long 0 onto the stack
3: lstore_2 // store a long value in a local variable 2 (s3)
4: goto 26
7: iconst_0 // push the int value 0 onto the stack
8: istore 4 // store int value into variable 4 (j)
10: goto 17
13: lload_2 // load a long value from a local variable 2 (s3)
14: lconst_1 // push the long 1 onto the stack
15: ladd // add two longs
16: lstore_2 // store a long value in a local variable 2 (s3)
17: iload 4 // load an int value from a local variable 4 (j)
19: iinc 4, 1 // increment local variable 4 (j) by signed byte 1
22: iload_0 // load an int value from a local variable 0 (L)
23: if_icmplt 13 // if value1 is less than value2, branch to instruction at 13
26: iload_1 // load an int value from a local variable 1 (i)
27: iinc 1, 1 // increment local variable 1 by signed byte 1
30: iload_0 // load an int value from a local variable 0 (L)
31: if_icmplt 7 // if value1 is less than value2, branch to instruction at 7
34: lload_2 // load a long value from a local variable 2 (s3)
35: lreturn // return a long value
为了使输出更具可读性,我根据Java bytecode instruction listings添加了描述每条指令的作用的注释。
如果仔细观察,您会发现这两个字节码之间存在重要差异。
while循环(对于for循环也是如此)在字节码的末尾定义了if语句(if_icmplt
指令)。这意味着要检查第一个循环的退出条件,必须调用goto到第26行,并且类似地转到第17行的第二个循环。
上述字节码是在Mac OS X上使用javac 1.6.0_45生成的。
<强>摘要强>
我认为不同的比较量加上while和for循环字节码中goto指令的存在是造成这些循环之间性能差异的原因。