views:

121

answers:

3

Does the java compiler (the default javac that comes in JDK1.6.0_21) optimize code to prevent the same method from being called with the same arguments over and over? If I wrote this code:

public class FooBar {
    public static void main(String[] args) {
        foo(bar);
        foo(bar);
        foo(bar);
    }
}

Would the method foo(bar) only run once? If so, is there any way to prevent this optimization? (I'm trying to compare runtime for two algos, one iterative and one comparative, and I want to call them a bunch of times to get a representative sample)

Any insight would be much appreciated; I took this problem to the point of insanity (I though my computer was insanely fast for a little while, so I kept on adding method calls until I got the code too large error at 43671 lines).

+4  A: 

It doesn't; that would cause a big problem if foo is non-pure (changes the global state of the program). For example:

public class FooBar {
    private int i = 0;
    private static int foo() {
        return ++i;
    }

    public static void main(String[] args) {
        foo();
        foo();
        foo();
        System.out.println(i);
    }
}
Michael Mrozek
And if "foo" didn't change the global states of the program? Also, I think `i` would have to be static to be changed by the `static` method `foo`
Rafe Kettler
It can if it can prove that foo() is pure, or that none of its side-effects matter; and the hotspot JVM does try to prove that foo() is pure and can be skipped. Remove the println() and hotspot is able to reduce the entire program to a nop, and most likely will (eventually).
Recurse
+5  A: 

The optimization you are observing is probably nothing to do with repeated calls ... because that would be an invalid optimization. More likely, the optimizer has figured out that the method calls have no observable effect on the computation.

The cure is to change the method so that it does affect the result of computation ...

Stephen C
If calls "have no observable effect on the computation", then there's no way to see whether any of those calls were made :) In either case, I've never heard about such optimization in java.
Nikita Rybak
Thanks, whether it's true or not, this helps... By making the input for each method random, I've been able to make them have *some* runtime. I still have to call the methods a lot so the law of averages can take effect and make the two methods' runtimes comparable.
Rafe Kettler
@Nikita - actually, there are two ways. 1) time the code - and see if zero calls and one call per loop take the same time. 2) get the JIT to dump the native code. (I forget how you do it ... but there's a way.)
Stephen C
@Nikita Rybak: It's called dead code removal, and it is used a lot. One of the recommended way to prevent this is doing e.g. `if(someComputation == 0) System.out.println("");` (recommended by the authors of Java Concurrency in Practice). This practice could then be used to detect whether the optimization has happened (at least in some situations..)
Enno Shioji
+1  A: 

You haven't provided enough information to allow for any definitive answers, but the jvm runtime optimizer is extremely powerful and does all sorts of inlining, runtime dataflow and escape analysis, and all manner of cache tricks.

The end result is to make the sort of micro-benchmarks you are trying to perform all but useless in practice; and extremely difficult to get right even when they are potentially useful.

Definitely read http://www.ibm.com/developerworks/java/library/j-benchmark1.html for a fuller discussion on the problems you face. At the very least you need to ensure:

  1. foo is called in a loop that runs thousands of times
  2. foo() returns a result, and
  3. that result is used

The following is the minimum starting point, assuming foo() is non-trivial and therefore is unlikely to be inlined. Note: You still have to expect loop-unrolling and other cache level optimizations. Also watch out for the hotspot compile breakpoint (I believe this is ~5000 calls on -server IIRC), which can completely stuff up your measurements if you try to re-run the measurements in the same JVM.

public class FooBar {
    public static void main(String[] args) {
        int sum = 0;
        int ITERATIONS = 10000;
        for (int i = 0; i < ITERATIONS; i++) {
            sum += foo(i);
        }

        System.out.println("%d iterations returned %d sum", ITERATIONS, sum);
    }
}

Seriously, you need to do some reading before you can make any meaningful progress towards writing benchmarks on a modern JVM. The same optimizations that allows modern Java code to match or even sometimes beat C++ make benchmarking really difficult.

Recurse
Thanks, very helpful. I've got some reading to do, though...
Rafe Kettler