我有一个简单的问题,关于 Sun 的 JDK 中提供的 Java 编译器有多“智能”。具体来说,提前评估 for() 循环的条件部分中出现的任何函数,而不是在循环的每次迭代中评估它们,是否足够聪明?
例如,考虑以下代码。
// Array of doubles to "hold" the array.
private double matrix[][];
public int getCols() {
// Compute the number of columns in a matrix.
}
public int getRows() {
// Compute the number of rows in a matrix.
}
// Compute the sum of all elements in the matrix.
public double sum() {
double result = 0;
for (int r = 0; r < getRows(); r++) {
for (int c = 0; c < getCols(); c++) {
result += this.matrix[r][c];
}
}
return result;
}
显然,我可以修改 sum() 方法,以确保 getRows() 和 getCols() 不会在循环的每次迭代中进行评估,方法是将其更改为
public double sum() {
double result = 0;
int numRows = getRows();
int numCols = getCols();
for (int r = 0; r < numRows; r++) {
for (int c = 0; c < numCols; c++) {
result += this.matrix[r][c];
}
}
return result;
}
但是,我想知道编译器是否足够聪明来预先评估这些本身。也就是说,它会自动发现提前评估条件中出现的任何函数比在每次迭代时评估它们在计算上更便宜吗?
一般来说,这样的优化是“错误的”。这些方法是虚拟的,因此它们可能会在子类中更改以执行完全不同的操作(例如返回随机数),并且可能很难甚至不可能静态地证明(是的,它需要是一个证明)它们返回相同的结果每次迭代的值,即使它们是 final
。事实上,也许他们根本不这样做(想想另一个线程并行改变行/列的数量 - 是的,在这种情况下你遇到了很多其他问题,但仍然需要考虑)。
除此之外,编译器不需要优化任何东西:在运行时,JIT 编译器可以优化更多。例如,它可以生成带有内联虚拟调用的代码,并且它可能(取决于代码)能够排除错误(至少如果它们无条件返回常量)。然而,如果不能改变语义,那么它也不会这样做。如果真的很重要,那就自己做吧。不管怎样,检查一下它是否是瓶颈。
getRows()
是如何实现的。如果每次调用它返回不同的值怎么办?
所以,底线。第二个版本的代码要好得多。不仅仅是从性能角度来看。它更具可读性,我相信您在编码时应该更喜欢这种方法。
我允许自己写入 for 循环的唯一方法调用是调用数组的
length()
方法。 Array 不是一个“真正的”类,它的 length() 方法也不是一个真正的方法,因此它的调用不会影响性能。
javac
不会优化此类事情。 JVM 可以。
这取决于您的
getRows()
和
getCols()
中的内容。如果它们非常简单,那么几乎可以肯定它们会被 JVM 内联;那么 JVM 可以进一步优化,调用它们一次并缓存结果,如果它可以断定这些值没有改变。例如下面的代码
for(int i=0; i<string.length(); i++)
char c = string.charAt(i);
将被 JVM 积极优化,我们的手动优化无法击败它。
public class Test {
private static int number = 5;
public static void main(String[] args) {
for(int i = 0; i < end(); i++) {
System.out.println(i);
}
}
public static int end() {
return number--;
}
}
输出:
0
1
2
这表明编译器每次都会计算表达式。
它应该足够聪明,可以让你的循环与
相同for (int r = 0, rmax=getRows(), cmax=getCols(); r < rmax; r++) {
double[] cs = this.matrix[r]
for (int c = 0; c < cmax-1; c+=2) {
result += cs[c] + cs[c+1];
}
if (cmax % 2 == 1)
result += cs[cmax-1];
}
我看到它循环展开代码,因此它在一个循环中执行两个循环。
您可能有兴趣下载 OpenJDK 的调试版本并使用 -XX:+PrintAssembly
http://wikis.sun.com/display/HotSpotInternals/PrintAssembly