Java循环效率

我正在比较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

同时: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循环比forwhile循环执行的比较少

为简单起见,假设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字节码指令列表的作用 。

如果仔细观察,您会发现这两个字节码之间存在重要差异。 while循环(对于for循环也是如此)在字节码的末尾定义了if语句( if_icmplt指令)。 这意味着要检查第一个循环的退出条件,必须调用goto到第26行,并且类似地转到第17行的第二个循环。

上面的字节码是在Mac OS X上使用javac 1.6.0_45生成的。

概要

我认为不同的比较量加上while和for循环字节码中goto指令的存在是造成这些循环之间性能差异的原因。