简体   繁体   中英

Automatic compiler optimizations in Java control structures?

I have a quick question about how "smart" the Java compiler provided in Sun's JDK is. Specifically, is it smart enough to evaluate any functions appearing in the conditional part of a for() loop ahead of time instead of evaluating them at every iteration of the loop?

For instance, consider the following code.

// 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;
}

Clearly, I could modify the sum() method to ensure that getRows() and getCols() aren't evaluated at each iteration of the loop by changing it to

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;
}

I wonder, however, if the compiler is smart enough to pre-evaluate these itself. That is, will it automatically spot that it's computationally cheaper to evaluate any functions that appear in conditionals ahead of time rather than evaluate them at each iteration?

Thanks!

Generally, such an optimization would be wrong . The methods are virtual, so they may be changed in a subclass to do something entirely different (such as returning random numbers) and it may be hard to impossible to statically prove (yes, it needs to be a proof) that they return the same value on every iteration even if they were final . In fact, maybe they don't do that at all (think another thread changing the number of rows/columns in parallel - yeah, you got an whole lot of other problems in that case, but it still needs to be considered).

And apart from that, the compiler doesn't need to optimize anything: At runtime, the JIT-compiler can optimize much more. For instance, it can generate code with the virtual calls inlines and it may (depending on the code) be able to factor out the wrong (at least if they unconditionally return a constant). If can't, however, change semantics, so again, it won't do this. If it's really important, do it yourself. Either way, check if it's a bottleneck at all.

I do not think that compiler can be so smart. It cannot know how method getRows() is implemented. What if it returns different values every time you call it?

So, bottom line. The second version of code is much better. Not only from the performance perspective. It is more readable and I believe you should prefer this method when you are coding.

The only method call I permit to myself to write into the for loop is a call of length() method of array. Array is not a "real" class and its length() method is not a real method, so its invocation does not affect performance.

javac doesn't optimize such things. JVM does.

It depends on what's in your getRows() and getCols() . If they are quite simple, it's almost certain that they will be inlined by JVM; then JVM could optimize further, call them once and cahe the result, if it can conclude that the values don't change.

For example, the following code

for(int i=0; i<string.length(); i++)
    char c = string.charAt(i);

will be aggressively optimized by JVM, no manual optimization by us could beat it.

It would be pretty horrible if the compiler would evaluate these expressions just once, because you may have situations where you want them to be evaluated more than once. But you can write a little test:

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--;
    }

}

Output:

0
1
2

This shows you that the compiler evaluates the expression every time.

The compiler does next to no optimisations however the Sun/Oracle compiler is smart enough to inline up to two "virtual" methods. Depending on the complexity of the getters, it likely to make no difference.

It should be smart enough to make your loops the same as

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];
}

I have seen it loop unroll the code so it performs two loops in one.

You might bet interested to download the debug version of the OpenJDK and use -XX:+PrintAssembly http://wikis.sun.com/display/HotSpotInternals/PrintAssembly

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM