Java API中的循环计数器

时间:2010-11-15 06:27:39

标签: java performance loops

所有

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

3 个答案:

答案 0 :(得分:7)

我用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)。这个典型(!)示例的主要区别在于,在第一个示例中,我们与0 ifge 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)

<强>加成

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

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

答案 1 :(得分:2)

如有疑问,请以基准为准。

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的早期阶段)可能存在一些性能伏都教,但在我看来,情况已经不再如此。

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

答案 2 :(得分:2)

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

这些序列可能是:

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

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

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

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

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