我比较
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生成的.
概要