Java API中的循环计数器

所有,

在浏览Java API中的一些文件时,我注意到许多实例,其中循环计数器正在递减而不是递增。 即在String类中的forwhile循环中。 虽然这可能是微不足道的,但递减计数器而不是增加是否有任何意义?

我用eclipse 3.6(java 6)编译了两个简单的循环,并查看字节代码是否有一些差异。 这是代码:

 for(int i = 2; i >= 0; i--){} for(int i = 0; i <= 2; i++){} 

这是字节码:

 // 1st for loop - decrement 2 -> 0 0 iconst_2 1 istore_1 // i:=2 2 goto 8 5 inc 1 -1 // i+=(-1) 8 iload_1 9 ifge 5 // if (i >= 0) goto 5 // 2nd for loop - increment 0 -> 2 12 iconst_0 13 istore_1 // i:=0 14 goto 20 17 inc 1 1 // i+=1 20 iload_1 21 iconst 2 22 if_icmple 17 // if (i <= 2) goto 17 

递增/递减操作应该没有区别,它是+1+(-1) 。 这个典型(!)示例的主要区别在于,在第一个示例中,我们将比较为0ifge i ),在第二个示例中我们将其 与值if_icmple i 2 )进行比较。 并且每次迭代都会完成同样的事情。 因此, 如果有任何(轻微的)性能增益,我认为这是因为与0进行比较然后与其他值进行比较的成本更低。 所以我猜这不是增加/减少,而是产生差异而是停止标准

因此,如果您需要在源代码级别上进行一些微优化,请尝试以与零比较的方式编写循环,否则请尽可能保持可读性(并且更容易理解递增):

  for (int i = 0; i <= 2; i++) {} // readable for (int i = -2; i <= 0; i++) {} // micro-optimized and "faster" (hopefully) 

加成

昨天我做了一个非常基本的测试 - 刚刚创建了一个2000x2000arrays并根据单元索引的计算填充了单元格,从0->1999计算行和单元格,另一次从1999->0倒计时。 我并不感到惊讶,两个场景都有类似的性能(在我的机器上185 ... 210毫秒)。

所以是的 ,字节码级别(eclipse 3.6)存在差异,但是,嘿,我们现在在2010年,现在似乎没有显着差异。 再次,使用斯蒂芬斯的话,“不要浪费你的时间”这种优化。 保持代码可读和易懂。

当有疑问时,基准。

 public class IncDecTest { public static void main(String[] av) { long up = 0; long down = 0; long upStart, upStop; long downStart, downStop; long upStart2, upStop2; long downStart2, downStop2; upStart = System.currentTimeMillis(); for( long i = 0; i < 100000000; i++ ) { up++; } upStop = System.currentTimeMillis(); downStart = System.currentTimeMillis(); for( long j = 100000000; j > 0; j-- ) { down++; } downStop = System.currentTimeMillis(); upStart2 = System.currentTimeMillis(); for( long k = 0; k < 100000000; k++ ) { up++; } upStop2 = System.currentTimeMillis(); downStart2 = System.currentTimeMillis(); for( long l = 100000000; l > 0; l-- ) { down++; } downStop2 = System.currentTimeMillis(); assert (up == down); System.out.println( "Up: " + (upStop - upStart)); System.out.println( "Down: " + (downStop - downStart)); System.out.println( "Up2: " + (upStop2 - upStart2)); System.out.println( "Down2: " + (downStop2 - downStart2)); } } 

使用以下JVM:

 java version "1.6.0_22" Java(TM) SE Runtime Environment (build 1.6.0_22-b04-307-10M3261) Java HotSpot(TM) 64-Bit Server VM (build 17.1-b03-307, mixed mode) 

具有以下输出(多次运行以确保加载JVM并确保数字稍微稳定下来)。

 $ java -ea IncDecTest Up: 86 Down: 84 Up2: 83 Down2: 84 

这些都非常接近彼此,我感觉任何差异都是JVM在某些点上加载某些代码而不是其他代码或者后台任务发生的错误,或者只是在毫秒边界上翻倒并向下舍入。

虽然在某一时刻(Java的早期阶段)可能存在一些性能巫术,但在我看来,情况已经不再如此。

您可以尝试运行/修改代码以便自己查看。

这可能是Sun工程师进行大量分析和微优化的结果,而您找到的那些示例就是结果。 它们也可能是Sun工程师基于对JIT编译器的深入了解而“优化”的结果……或者基于浅/不正确的知识/巫毒思维。

这些序列可能是这样的:

  • 比增量循环快
  • 没有比增量循环更快或更慢,或
  • 比最新的JVM的增量循环慢,并且代码不再是最佳的。

无论哪种方式, 您都不应该在代码中模拟这种做法 ,除非使用最新的JVM进行彻底的分析certificate:

  • 你的代码真的会受益于优化,以及
  • 递减循环确实比您的特定应用程序的递增循环更快。

即便如此,您可能会发现您的精心优化的代码在其他平台上并不是最佳的……并且您需要重复这个过程。

目前,人们普遍认为最好的第一个策略是编写简单的代码并将优化留给JIT编译器。 编写复杂的代码(例如反向运行的循环)实际上可能会影响JIT编译器的优化尝试。