简单的循环性能问题


Simple loop performance issue

假设我有一个像这样的简单PHP循环

// Bad example
$array = array('apple','banana','cucumber');
for ($i = 1; $i < count($array); $i++) {
    echo $array[$i];
}

我知道这是不好的做法。在循环中使用count()最好是而不是

// Nice example
$array = array('apple','banana','cucumber');
$limit = count($array);
for ($i = 1; $i < $limit; $i++) {
    // do something...
}    

在Java中,我将这样做

// Bad example?
String[] array = {"apple","banana","cucumber"};
for(int i = 0; i < array.length; i++){
    System.out.println(array[i]);
}

问题:这不是一个不好的做法吗?还是和下面的例子一样?

// Nice example?
String[] array = {"apple","banana","cucumber"};
int limit = array.length;
for(int i = 0; i < limit; i++){
    System.out.println(array[i]);
}

任何像样的编译器/解释器都应该自动优化第一个示例以匹配第二个示例(无论如何从语义上讲,如果不是完全从字面上看),并且可能优化第三个示例以匹配第四个示例。它被称为循环不变优化,其中编译器识别出实体(变量,表达式等)在循环内不会变化(即不变)并将其移到循环外(松散地说)。

您使用的"坏"示例是不相等的,因此不具有可比性-即使它们表面上看起来是这样。使用下面的描述:

for (initialization; termination; increment) {
    statement(s)
}

(这是PHP和java循环的描述),初始化语句在循环开始时执行一次。终止语句和递增语句在循环的每次迭代中执行。

终止语句中使用PHP的count是不好的做法的原因是,对于每次迭代,都会发生count函数调用。在Java示例中,array.length不是函数调用,而是对公共成员的引用。因此,示例中使用的终止语句不是等效行为。我们期望函数调用比属性引用开销更大。 在中的任何具有所描述的循环机制的语言中,在for循环的终止语句中放置函数调用(或调用掩盖函数的属性)都是不好的做法。这就是PHP示例"糟糕"的原因,如果在Java for循环的终止语句中使用count类型的函数,情况也会同样糟糕。那么,真正的问题是,Java的Array.length是否确实掩盖了函数调用—答案是"否"(请参阅潜在的重复问题,和/或查看http://leepoint.net/notes-java/data/arrays/arrays.html)

主要区别在于count()是一个函数,而array.length是一个属性,因此与limit变量没有什么不同。

它们是不一样的,在Java的"好例子"中,你不是每次都计算数组的长度。相反,您将其存储在limit变量中,并使用它来停止计算,而不是通过for循环在每次迭代中调用数组上的length函数的结果。

编辑:你认为是"坏做法"的两件事都是坏做法,而"好例子"是更有效的方法(至少在理论上)。但是,在实现上确实不会有任何明显的区别。

在java中这并不重要,数组将此属性作为常量(public final int)。不同之处在于java数组有一个固定的大小,不能增长,所以不需要每次都计算元素来访问长度。