Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is Java jitting inefficient code to run faster than efficient code?

In the following code snippet, Foo1 is a class that increments a counter every time the method bar() is called. Foo2 does the same thing but with one additional level of indirection.

I would expect Foo1 to be faster than Foo2, however in practice, Foo2 is consistently 40% faster than Foo1. How is the JVM optimizing the code such that Foo2 runs faster than Foo1?

Some Details

  • The test was executed with java -server CompositionTest.
  • Running the test with java -client CompositionTest produces the expected results, that Foo2 is slower than Foo1.
  • Switching the order of the loops does not make a difference.
  • The results were verified with java6 on both sun and openjdk's JVMs.

The Code

public class CompositionTest {

    private static interface DoesBar {
        public void bar();
        public int count();
        public void count(int c);
    }

    private static final class Foo1 implements DoesBar {
        private int count = 0;
        public final void bar() { ++count; }
        public int count() { return count; }
        public void count(int c) { count = c; }
    }

    private static final class Foo2 implements DoesBar {
        private DoesBar bar;
        public Foo2(DoesBar bar) { this.bar = bar; }
        public final void bar() { bar.bar(); }
        public int count() { return bar.count(); }
        public void count(int c) { bar.count(c); }
    }

    public static void main(String[] args) {
        long time = 0;
        DoesBar bar = null;
        int reps = 100000000;

        for (int loop = 0; loop < 10; loop++) {
            bar = new Foo1();
            bar.count(0);

            int i = reps;
            time = System.nanoTime();
            while (i-- > 0) bar.bar();
            time = System.nanoTime() - time;

            if (reps != bar.count())
                throw new Error("reps != bar.count()");
        }
        System.out.println("Foo1 time: " + time);

        for (int loop = 0; loop < 10; loop++) {
            bar = new Foo2(new Foo1());
            bar.count(0);

            int i = reps;
            time = System.nanoTime();
            while (i-- > 0) bar.bar();
            time = System.nanoTime() - time;

            if (reps != bar.count())
                throw new Error("reps != bar.count()");
        }
        System.out.println("Foo2 time: " + time);
    }
}
like image 989
TianyuZhu Avatar asked May 17 '12 23:05

TianyuZhu


People also ask

Can JIT be faster than compiled?

JIT code generally offers far better performance than interpreters. In addition, it can in some cases offer better performance than static compilation, as many optimizations are only feasible at run-time: The compilation can be optimized to the targeted CPU and the operating system model where the application runs.

Why is JIT faster than AOT?

In theory, a Just-in-Time (JIT) compiler has an advantage over Ahead-of-Time (AOT) if it has enough time and computational resources available. A JIT compiler can be faster because the machine code is being generated on the exact machine that it will also execute on.

Is Java with JIT faster than C++?

In theory, yes. With just-in-time compilation the compiler (Java Runtime) can theoretically produce better optimised code because the code can be optimised for characteristics of that particular run.

Is JIT compilation slow?

JIT compilers mostly compile quickly and skip some optimizations that take longer to find. VM's often enforce safety and this slows execution.


2 Answers

Your microbench mark is meaningless. On my computer the code runs in about 8ms for each loop... To have any meaningful number a benchmark should probably run for at least a second.

When run both for around a second (hint, you need more than Integer.MAX_VALUE repetitions) I find that the run times of both are identical.

The likely explanation for this is that the JIT compiler has noticed that your indirection is meaningless and optimised it out (or at least inlined the method calls) such that the code executed in both loops is identical.

It can do this because it knows bar in Foo2 is effectively final, it also know that the argument to the Foo2 constructor is always going to be a Foo1 (at least in our little test). That way it knows the exact code path when Foo2.bar is called. It also knows that this loop is going to run a lot of times (in fact it knows exactly how many times the loop will execute) -- so it seems like a good idea to inline the code.

I have no idea if that is precisely what it does, but these are all logical observations that the JIT could me making about the code. Perhaps in the future some JIT compilers might even optimise the entire while loop and simply set count to reps, but that seems somewhat unlikely.

like image 82
Dunes Avatar answered Sep 18 '22 05:09

Dunes


Trying to predict performance on modern languages is not very productive.

The JVM is constantly modified to increase performance of common, readable structures which, in contrast, makes uncommon, awkward code slower.

Just write your code as clearly as you can--then if you really identify a point where your code is actually identified as too slow to pass written specifications, you may have to hand-tweak some areas--but this will probably involve large, simple ideas like object caches, tweaking JVM options and eliminating truly stupid/wrong code (Wrong data structures can be HUGE, I once changed an ArrayList to a LinkedList and reduced an operation from 10 minutes to 5 seconds, multi-threading a ping operation that discovered a class-B network took an operation from 8+ hours to minutes).

like image 31
Bill K Avatar answered Sep 21 '22 05:09

Bill K