tags:

views:

476

answers:

8

I have heard that Java must use a JIT to be fast. This makes perfect sense when comparing to interpretation, but why can't someone make an ahead-of-time compiler that generates fast Java code? I know about gcj, but I don't think its output is typically faster than Hotspot for example.

Are there things about the language that make this difficult? I think it comes down to just these things:

  • Reflection
  • Classloading

What am I missing? If I avoid these features, would it be possible to compile Java code once to native machine code and be done?

+9  A: 

A JIT compiler can be faster because the machine code is being generated on the exact machine that it will also execute on. This means that the JIT has the best possible information available to it to emit optimized code.

If you pre-compile bytecode into machine code, the compiler cannot optimize for the target machine(s), only the build machine.

Andrew Hare
Nice one there. Clear and concise.
o.k.w
How does cross compiling work then?
Ibrahim
Why do we not use a JIT for C/C++ then? I guess that's where LLVM comes in.
Adam Goode
@Ibrahim - we are talking here about variations in instruction sets; e.g. which of the many extensions to the x86 instruction set are available on the execution platform.
Stephen C
Ahead of time compilers can still match that. The Intel C++ compiler, for example, can be told to emit multiple versions of a piece of code, each one tuned for a slightly different processor target. It'll add code to autodetect the processor at startup and select the most appropriate code path.
Boojum
@Adam. The traditional approach to C programs has been that the object file is native code with no further processing. This makes sense if CPU-time is a premium, which it used to be in the old days. Today memory access is the premium on PC's, so it doesn't matter if you spend a lot of effort post processing.
Thorbjørn Ravn Andersen
Almost correct, you can optimize for _a_ target machine, most compilers will allow you to specify the target architecture. However you can only optimize for _one_ target machine, a JIT will always optimize for the target machine.
Paul Wagland
How much speedup will this precise targeting gain? When Fedora benchmarked tuning these settings, they only saw 1-2% gains: https://www.redhat.com/archives/fedora-devel-list/2009-June/msg01506.html
Adam Goode
+3  A: 

Java's ability to inline across virtual method boundaries and perform efficient interface dispatch requires runtime analysis before compiling - in other words it requires a JIT. Since all methods are virtual and interfaces are used "everywhere", it makes a big difference.

280Z28
@28 - in theory, you can figure out (conservatively) the complete set of classes that could be used in a given Java program, simply by examining the source code or bytecode. Therefore, you COULD do these optimizations statically.
Stephen C
You're still compiling most everything. e.g. method calls based upon IO.
Xepoch
Some C++ compilers will use profiling information. Static analysis can be used to inline many virtual method calls.
Tom Hawtin - tackline
+5  A: 

In the end it boils down to the fact that having more information enables better optimizations. In this case, the JIT has more information about the actual machine the code is running on (as Andrew mentioned) and it also has a lot of runtime information that is not available during compilation.

Tal Pressman
+7  A: 

Java's JIT compiler is also lazy and adaptive.

Lazy

Being lazy it only compiles methods when it gets to them instead of compiling the whole program (very useful if you don't use part of a program). Class loading actually helps make the JIT faster by allowing it to ignore classes it hasn't come across yet.

Adaptive

Being adaptive it emits a quick and dirty version of the machine code first and then only goes back and does a through job if that method is used frequently.

Luke Quinane
Another aspect of its adaptiveness is that it can gather stats on the likely outcome of tests / branches while interpreting bytecodes, and feed this into the JIT compiler to produce better code.
Stephen C
+2  A: 

In theory, a JIT compiler has an advantage over AOT if it has enough time and computational resources available. For instance, if you have an enterprise app running for days and months on a multiprocessor server with plenty of RAM, the JIT compiler can produce better code than any AOT compiler.

Now, if you have a desktop app, things like fast startup and initial response time (where AOT shines) become more important, plus the computer may not have sufficient resources for the most advanced optimizations.

And if you have an embedded system with scarce resources, JIT has no chance against AOT.

However, the above was all theory. In practice, creating such an advanced JIT compiler is way more complicated than a decent AOT one. How about some practical evidence?

Dmitry Leskov
Hmm, that is a interesting link, but I would be more interested to see a comparison with gcj instead of gcc.
Adam Goode
Stefan's previous benchmarking session (http://stefankrause.net/wp/?p=6) included gcj and Apache Harmony, but it is a bit more outdated. Also, comparisons with those implementations are not perfectly correct as they are not tested for compliance with the Java SE spec. There are some overheads in a fully compliant implementation, one of them related to stack overflow handling (pun intended :) ).
Dmitry Leskov
+1  A: 

The real killer for any AOT compiler is:

Class.forName(...)

This means that you cannot write a AOT compiler which covers ALL Java programs as there is information available only at runtime about the characteristics of the program. You can, however, do it on a subset of Java which is what I believe that gcj does.

Another typical example is the ability of a JIT to inline methods like getX() directly in the calling methods if it is found that it is safe to do so, and undoing it if appropriate, even if not explicitly helped by the programmer by telling that a method is final. The JIT can see that in the running program a given method is not overriden and is therefore in this instance can be treated as final. This might be different in the next invocation.

Thorbjørn Ravn Andersen
Yes, this is basically runtime classloading, right?
Adam Goode
All class loading happen at runtime. I do not understand your comment.
Thorbjørn Ravn Andersen
I mean, classloading that couldn't be predicted at bytecode-generation time. Class.forName takes a string and produces a class. There is no way to know what class it might be. If you didn't do this, a AOT compiler could know all the classes you might use and do some optimizations then, right?
Adam Goode
Yes, this is exactly why it breaks.
Thorbjørn Ravn Andersen
+1  A: 

I think the fact that the official Java compiler is a JIT compiler is a large part of this. How much time has been spent optimizing the JVM vs. a machine code compiler for Java?

Brendan Long
+1  A: 

JITs can identify and eliminate some conditions which can only be known at runtime. A prime example is the elimination of virtual calls modern VMs use - e.g., when the JVM finds an invokevirtual or invokeinterface instruction, if only one class overriding the invoked method has been loaded, the VM can actually make that virtual call static and is thus able to inline it. To a C program, on the other hand, a function pointer is always a function pointer, and a call to it can't be inlined (in the general case, anyway).

Here's a situation where the JVM is able to inline a virtual call:

interface I { 
    I INSTANCE = Boolean.getBoolean("someCondition")? new A() : new B();
    void doIt(); 
}
class A implements I { 
    void doIt(){ ... } 
}
class B implements I { 
    void doIt(){ ... } 
}
// later...
I.INSTANCE.doIt();

Assuming we don't go around creating A or B instances elsewhere and that someCondition is set to true, the JVM knows that the call to doIt() always means A.doIt, and can therefore avoid the method table lookup, and then inline the call. A similar construct in a non-JITted environment would not be inlinable.

gustafc