7

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).

Rafe Kettler
  • 75,757
  • 21
  • 156
  • 151
  • I don't really see why the compiler would remove method calls like you are suggesting. I don't know the actual answer but there is a 64k limit on the code inside methods. So the compiler isn't removing your calls its just reaching the limit on the size of the code inside your method. It would do that even if you had other lines of code inside that method. – Matt Phillips Aug 02 '10 at 03:18

4 Answers4

6

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
  • 698,415
  • 94
  • 811
  • 1,216
  • 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 Aug 02 '10 at 03:33
  • 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 Aug 02 '10 at 03:34
  • @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 Aug 02 '10 at 04:12
  • @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 Aug 02 '10 at 04:39
4

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
  • 169,610
  • 28
  • 168
  • 175
  • 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 Aug 02 '10 at 03:19
  • 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 Aug 02 '10 at 04:02
4

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
  • 3,557
  • 1
  • 23
  • 36
0

The Java compiler is not allowed to perform such optimizations because method calls very likely cause side effets, for example IO actions or changes to all fields it can reach, or calling other methods that do so.

In functional languages where each function call is guaranteed to return the same result if called with the same arguments (changes to state are forbidden), a compiler might indeed optimize away multiple calls by memorizing the result.

If you feel your algorithms are too fast, try to give them some large or complicated problem sets. There are only a few algorithms which are always quite fast.

MauganRa
  • 494
  • 6
  • 8