所有
在浏览Java API中的一些文件时,我注意到许多实例,其中循环计数器正在递减而不是递增。即在String类的for
和while
循环中。虽然这可能是微不足道的,但递减计数器而不是递增有什么意义吗?
答案 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进行彻底的分析证明:
即便如此,您可能发现您的精心优化的代码在其他平台上并不是最佳的......并且您需要重复这一过程。
现在,人们普遍认为最好的第一个策略是编写简单的代码并将优化留给JIT编译器。编写复杂的代码(例如反向运行的循环)实际上可能会阻碍JIT编译器优化的尝试。