我比较
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循环:6.48300
do-while:0.41200
而:9.71500
为什么do-while循环要快得多?这种性能差距与L的任何变化并行扩展.我独立运行这些循环,并且执行相同.
解决方法
我已经运行了你提供的代码,也惊讶地发现这些性能差异.由好奇心引导我开始调查,发现确实尽管这些循环似乎在做同样的事情,但是它们之间有一些重要的区别.
第一次运行这些循环后的结果是:
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 < 1
内循环:0 < 1
内环:1 < 1
外环:1 < 1 4次比较
int i = 0; long s2 = 0; do { i++; int j = 0; do { s2 += 1; j++; } while (j < L); } while (i < L);
内环:1 < 1
外环:1 < 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指令).这意味着要检查第一个循环的退出条件,必须调用到第26行的转换,并且类似地,转到第二个循环的第17行.
上述字节码是在Mac OS X上使用javac 1.6.0_45生成的.
概要